Managing types in a monorepo using Typescript and dealing with circular dependency problems during type imports

As detailed in this answer, Typescript 3.8 brought about:

import type

to safely import definitions (source):

import type only imports declarations for type annotations and declarations. It's completely erased, leaving no trace at runtime. Equally, export type offers an export for type contexts and is also removed from TypeScript's output.

Nevertheless, you still need to have the package as a dependency.

This can result in circular dependencies, as illustrated in my scenario. A simplified breakdown of my monorepo involves:

  • client-web: a web client using vite (client)
  • client-store: a redux store package (model)
  • image-gallery: an image gallery package (presentation)

All of them require awareness of the following type:

type IImage = {
     id: string;
     title: string;
     url: string;
     urlThumb: string;
}

However, the location where this type should "live" isn't clearly defined. There are a few potential options:

  1. place it in the presentation => image-gallery and import it into the other packages
  2. put it in the model => client-store and import it into the other packages
  3. establish a shared common-types package (manually)
  4. generate an automatically shared common-types package (composition)

No matter which route you take, there may be challenges like complicating your dependency graph and transitioning from parallel to sequential build processes. Additionally, not all types are the same - sometimes you want types near a component, while other times you prefer grouping types by semantic context.

I'm curious if there's a solution that hasn't crossed my mind?

Answer №1

It is widely known that circular dependencies in JavaScript and TypeScript occur when two modules import each other. What needs clarification is that these modules may not be directly reliant on one another. For instance:

// Module A requires B from BC
A -> BC
// Module B requires A from module A
BC -> A

It should be noted that the actual dependencies are not circular. The circular dependency arises due to the structure of the files/modules. There are two potential solutions in such cases:

  1. Separate the common dependency into its own file.
  2. Consolidate the dependencies in a single file.

In the earlier example, this can be represented as follows:

// 1.
A -> C
B -> A // No circular dependency

// 2.
ABC // No external dependencies at all!

The IImage entity seems to be an independent component that could be extracted into its own file/module. Depending on the monorepo management tool being used, it could be moved to a separate file within one of the projects. Remember, circular dependencies occur between files, not projects, so this solution is deemed safe.

If the monorepo tool prohibits dependencies between non-library modules, the only option may be to create a distinct subproject.

In either scenario with the monorepo tool, opting for the latter approach is recommended as it ensures future scalability by consolidating common code in a dedicated subproject to prevent any potential circular dependencies down the line.

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

Encountering a WriteableDraft error in Redux when using Type Definitions in TypeScript

I'm facing a type Error that's confusing me This is the state type: export type Foo = { animals: { dogs?: Dogs[], cats?: Cats[], fishs?: Fishs[] }, animalQueue: (Dogs | Cats | Fishs)[] } Now, in a reducer I&a ...

When the value is empty, MUI Autocomplete will highlight all items

I have encountered a specific issue. I am working on developing a custom Autocomplete Component for filtering purposes. However, I recently came across the following Warning. MUI: The value provided to Autocomplete is invalid. None of the options matc ...

Implementing TypeScript for augmented styling properties in a component - a guide

I have custom components defined as follows: import React from 'react'; import styled from '../../styled-components'; const StyledInput = styled.input` display: block; padding: 5px 10px; width: 50%; border: none; b ...

tips for accessing dynamic key pair value data in Angular

I am facing an issue where I cannot retrieve the dynamic key pair value from the dynamic JSON. Below is my JSON: var d = { "pexels-photo.jpeg": { "information": "laptop", "desc": { "mimetype": "image/jpeg", "id" ...

The variable 'string' has been declared, but it is never utilized or accessed

Currently delving into Typescript and facing an early error. Despite following a tutorial, I'm encountering multiple errors that I have attempted to comment out. Would greatly appreciate it if someone could shed some light on why these errors are occu ...

Having trouble building the React Native app after adding react-native-vector icons?

A recent project I've been working on involved adding react-native-vector-icons using react-native 0.63.4. However, during the build process, I encountered an error when running the command npx react-native run-ios in the terminal. The warnings/errors ...

When attempting to save my submission object data to the database, TypeORM unexpectedly alters the information

I am encountering an issue with inserting my data into a database using TypeORM The problem at hand is as follows: What needs to be sent to the database includes the following data: Title, Description, Userid, idCategoryService, and createdBy. The ids and ...

Using Angular i18n to create dynamic forms from an array of objects

After receiving an object from the API, I created a form using an array of objects in TypeScript. Although the form is rendered correctly, it fails to translate when I try to localize the application. import { SpecializedAreasComponents } from 'src/a ...

Enforce directory organization and file naming conventions within a git repository by leveraging eslint

How can I enforce a specific naming structure for folders and subfolders? I not only want to control the styling of the names (kebab, camel), but also the actual names of the folders and files themselves. For example, consider the following paths: ./src/ ...

The inRequestScope feature seems to be malfunctioning and is not functioning as intended

Need help with using inRequestScope in inversifyJS For example: container.bind<ITransactionManager>(Types.MysqlTransactionManager).to(MysqlTransactionManager).inRequestScope() ... container.get<ITransactionManager>(Types.MysqlTransactionMana ...

TypeScript raises an issue with a Vue component property that has been defined using vue-property-decorator

I have a Vue component with a property defined using a decorator: import { Component, Vue } from "vue-property-decorator" @Component({ props: { myId: String, }, }) class TestProp extends Vue { myFuncti ...

Changing the order of a list in TypeScript according to a property called 'rank'

I am currently working on a function to rearrange a list based on their rank property. Let's consider the following example: (my object also contains other properties) var array=[ {id:1,rank:2}, {id:18,rank:1}, {id:53,rank:3}, {id:3,rank:5}, {id:19,r ...

What is the best way to send multiple values from the view to a method without using two-way binding?

https://i.sstatic.net/X4ivP.png When I change the dropdown value for the route type in my code, I need to pass both the gender value and the route type ID to my data retrieval method. Currently in my HTML file, I have only written a change event. I attem ...

Creating optional method parameters in Typescript based on their data type

In my method, the id is only available when it is of type B. See below (index: string, type: ResourceType.A, data: any): JSX.Element; and (index: string, type: ResourceType.B, data: any, id: string): JSX.Element; I attempted to create a method overload l ...

Encountering a situation where the data retrieved from Firestore in Angular TypeScript is returning as

I am encountering an issue with retrieving the eventID value from my Events collection in Firestore. Although I am able to successfully display all the events in my app, I require the eventID for additional functionalities. As someone new to TypeScript an ...

Organize library files into a build directory using yarn workspaces and typescript

When setting up my project, I decided to create a yarn workspace along with typescript. Within this workspace, I have three folders each with their own package.json /api /client /lib The main goal is to facilitate code sharing between the lib folder and b ...

Ensuring TypeORM constraint validations work seamlessly with MySQL and MariaDB

I recently started using TypeORM and I'm trying to incorporate the check decorator in my MySQL/MariaDB database. However, after doing some research on the documentation and online, it seems that the check decorator is not supported for MySQL. I'v ...

Can a single global variable be shared between a JavaScript file and a TypeScript file within an Angular project?

In my Angular 5 project, I am implementing a javascript library. If I create a global variable in my .js file, how can I access that variable from my .ts file? ...

What is the correct method for exporting a file from the temporary folder of a node server using res.sendFile(...) or res.download(...) over HTTP?

I am facing an issue with my node/express app where the excel file I generate downloads with zero bytes via http. How can I ensure that the downloaded file has the actual data in it? My node/express code is responsible for creating an excel file and stori ...

Preventing Multiple Login Attempts in Angular.js Using Typescript

Is there a way to maintain the user login attempts limit even after page refresh? login() { /* Check if user has fewer than 5 failed login attempts */ if (this.failedAttempts < 4) { this.auth.login(this.credentials).subscribe(() => { this.rou ...