Discover the use of dot notation for accessing nested properties

In the deps array below, I aim to enforce type safety. Only strings allowed should be in dot notation of ${moduleX}.${moduleX service}

// Modules each have a factory function that can return a services object (async)
createModules({
  data: {
    factory: () => Promise.resolve(() => {
      return {
        services: {
          createResource: () => {}
        },
      };
    })
  },
  dependentModule: {
    deps: ['data.createResource'], // <!-- should allow this
    factory: () => Promise.resolve(() => ({}))
  },
  incorrectConfig: {
    deps: ['data.serviceDoesNotExist'], // <-- should error here
    factory: () => Promise.resolve(() => ({}))
  }
});

function createModules<M>(config: Config<M>) {}

export type Config<M> = {
  [K in keyof M]: ModuleConfig<M>
}

export type Config<M> = {
  [K in keyof M]: {
    deps?: (`${keyof M & string}.${keyof (Awaited<M[keyof M]['factory']>['services']) & string}`)[]
    factory: () => Promise<(...args: any) => { services?: { [key: string]: any } }>
  }
}

I currently permit data.serviceDoesNotExist because it's merely inferred as a string:

deps?: (`data.${string}` | `dependentModule.${string}` | `incorrectConfig.${string}`)[] | undefined

How can I improve the inference to obtain the actual property name?

Playground

Answer №1

There are likely multiple approaches to tackle this issue. Here is my proposed method.

To handle the module strings and service strings, I suggest using two generic types named M and S.

function createModules<
  M extends string, 
  S extends string
>(config: Config<M, S>) {}

The type Config will be a Record where keys are of type M and values are instances of ModuleConfig, taking both M and S.

export type Config<
  M extends string, 
  S extends string
> = Record<M, ModuleConfig<M, S>>

Within the ModuleConfig, we can deduce S as the keys within the services object.

export type ModuleConfig<
  M extends string, 
  S extends string,
> = {
  deps?: /* ... */
  factory: () => Promise<(...args: any) => { services?: Record<S, any> }>
};

In regards to the deps property, it becomes more complicated. There is an issue with TypeScript inferring the dynamic strings instead of object keys when using template string literals directly. To address this, I introduced a utility type called Copy.

type Copy<S extends string> = S extends infer U extends string ? U : never

export type ModuleConfig<
  M extends string, 
  S extends string,
> = {
  deps?: (`${Copy<M>}.${Copy<S>}`)[]
  factory: () => Promise<(...args: any) => { services?: Record<S, any> }>
};

This refinement should solve the issue at hand.

Playground


Edit:

Here is another solution that tackles the issues brought up in the comments.

type Factory = () => Promise<(...args: any) => { services?: Record<string, any> }>

type ExtractServiceKeys<T extends Factory> = 
  keyof ReturnType<Awaited<ReturnType<T>>>["services"] & string

function createModules<
  T extends Record<string, {
    deps?: ({ 
      [K in keyof T]: `${K & string}.${ExtractServiceKeys<T[K]["factory"]>}`
    }[keyof T])[],
    factory: Factory
  }>,
>(config: T) {}

Playground

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

React Typescript: exploring the power of dynamic types

Can dynamic typing be implemented? The JSON structure I am working with looks like this: { "fieldName": "Some text", "type": String, "inputType": "text" }, { "fieldName": "Some bool&q ...

Tips for modifying the data type of a property when it is retrieved from a function

Currently, I have a function called useGetAuthorizationWrapper() that returns data in the format of { data: unknown }. However, I actually need this data to be returned as an array. Due to the unknown type of the data, when I try to use data.length, I enc ...

What is the process for creating accurate types for my package?

Currently, I am in the process of creating an npm package to be used by other developers within my company. While most aspects are functioning smoothly, I am facing challenges with getting the declarations right. I have experimented with various methods f ...

Error in TypeScript - Anticipated 1-2 arguments, received either none or multiple. Code Issue TS2556

While working in JavaScript, I had no issues with this code snippet. However, when I attempted to implement it in a TypeScript Project, an error surfaced. The problem seems to revolve around the fetch(...args) function call. const fetcher = (...args) =&g ...

Is it possible to optimize the performance of my React and TypeScript project with the help of webpack?

I am working on a massive project that takes 6 to 8 minutes to load when I run npm start. Is there a way to speed up the loading process by first displaying the sign-in page and then loading everything else? ...

How can one trigger a service method in nestjs through a command?

I am looking to run a service method without relying on API REST - I need to be able to execute it with just one command ...

What is the method for generating a data type from an array of strings using TypeScript?

Is there a more efficient way to create a TypeScript type based on an array of strings without duplicating values in an Enum declaration? I am using version 2.6.2 and have a long array of colors that I want to convert into a type. Here is what I envision: ...

Typescript's tree-pruning strategy design for optimization

I've been working on developing a library that enforces the use of specific strategies for each target. The current structure I have in place is as follows: [Application] -> contains -> [player] -> contains -> [renderer] In the current s ...

Develop interactive web applications using Typescript

Having difficulty compiling and executing the project correctly in the browser. The "master" branch works fine, but I'm currently working on the "develop" branch. It's a basic web project with one HTML file loading one TS/JS file that includes i ...

A step-by-step guide to showcasing dates in HTML with Angular

I have set up two datepickers in my HTML file using bootstrap and I am attempting to display a message that shows the period between the first selected date and the second selected date. The typescript class is as follows: export class Datepicker { ...

Facing issues updating the parent state value while using NextJs with React

I recently started working with NextJS and React, and I'm using trpc along with useQuery to fetch a list of users. After fetching the user list, I need to filter it based on the user's name. Below is a snippet of the code I've been working ...

The Static Interface Binding in TypeScript

I have inquired about how to extend the static functionality of existing objects in JavaScript (using TypeScript). In all examples provided here, I am utilizing Object The code below showcases a polyfill definition for ECMAScript's Object.is function ...

Zod vow denial: ZodError consistently delivers an empty array

My goal is to validate data received from the backend following a specific TypeScript structure. export interface Booking { locationId: string; bookingId: number; spotId: string; from: string; to: string; status: "pending" | "con ...

The event listener for 'end' is not executing in a Node.js Firebase and Nylas Express application

I am currently working on setting up webhooks with Nylas. In their provided example, there is a middleware code that I am implementing in my TypeScript project using Firebase as the endpoint. When testing locally with ngrok, the middleware functions prop ...

Can't figure out why the BackgroundImage URL from process.env isn't appearing on my website

Having trouble setting a background image on my website that connects with my backend to determine which image should appear. However, in some cases, the image isn't showing up. When attempting to add a background image using the code below within a ...

When working with Typescript, an error may occur related to index types even though the constant object and its

I am struggling with understanding TypeScript, specifically when it comes to a problem I encountered. Hopefully, someone can shed some light on this for me. My issue revolves around a functional component that is responsible for displaying the correct com ...

The attribute 'sandwiches' cannot be found within the data type 'string'

In my app, I require an object that can store strings or an array of strings with a string key. This object will serve as a dynamic configuration and the keys will be defined by the user, so I cannot specify types based on key names. That's why I&apos ...

Can you reach a screen prior to the stack navigator being established?

I'm diving into the world of React and decided to use Expo for building an app. I went with the TypeScript setup that comes with pre-implemented tabs and navigator by running "expo init newApp". Now, I just need a transition screen to display briefly ...

The React useEffect() hook causing an infinite re-render when trying to fetch all data regardless of

Recently, I've begun diving into React and utilizing the useEffect hook to fetch news and events from a database upon page load. However, when attempting to add a loading spinner, I encountered an unexpected infinite loop issue that has left me scratc ...

Resolving ES6 type conflicts while compiling TypeScript to Node.js

I seem to be facing some challenges with the TypeScript 2 type system when working with Node.js. Let me explain the situation: I am compiling a small Node.js Express server written in TypeScript to plain ES5 for running under Node 6.10.0 (target: ES5 in ...