Uncovering the origins of computed object keys in TypeScript

I am currently working on a project where I need to easily define and use new plugins using TypeScript in my IDE. My folder structure looks like this:

src
│ ...
└── plugins
   └── pluginA
   |     index.ts
   └── pluginB
   |     index.ts
   └── ...
     index.ts <-- "here I want to combine all plugins in one object"

Each plugin has a default export in index.ts with an interface as follows:

interface Plugin {
  name: string
  ...
}

src/plugins/pluginA/index.ts

export default {
  name: "pluginA",
  ...
}

src/plugins/index.ts

import pluginA from "./pluginA"
import pluginB from "./pluginB"
...

export default {
  [pluginA.name]: pluginA,
  [pluginB.name]: pluginB,
  ...
}

I aim for TypeScript to be able to understand the names of existing plugins (to infer the Literal type of keys of the plugins).

This means that when using plugins in my code, I want it to look something like this:

import plugins from "src/plugins"

...

const plugin = plugins["nonExistingPlugin"] // TypeScript should show error here if no plugin exists with this name

const anotherPlugin = plugins["plug_"] // I expect the IDE to autocomplete the name of the plugin from the existing ones
//                            "pluginA"
//                            "pluginB"
//                            "..."

All my attempts so far have led me to TypeScript recognizing the `name` property of the plugin as a `string`, but not inferring a Literal type.

Is there a way for TypeScript to achieve this? And if not, are there any other methods to accomplish this goal?

Answer №1

You have the option to utilize as const assertions in this scenario:

const addonA = {
  name: 'addonA',
  //
} as const
const addonB = {
  name: 'addonB',
  //
} as const
const addonC = {
  name: 'addonC',
  //
} as const

const addons = {
  [addonA.name]: '',
  [addonB.name]: '',
  [addonC.name]: '',
}
const addon = addons["nonExistingAddon"] 

// Expected type error occurs
// Element implicitly has an 'any' type due to inability to index type '{ addonA: string; addonB: string; addonC: string; }' with expression of type '"nonExistingAddon"'.
//  Property 'nonExistingAddon' does not exist on type '{ addonA: string; addonB: string; addonC: string; }'.ts(7053)
const addon = addons["addon"] // Autocomplete options for `addonA`, `addonB`, `addonC` are displayed by the Editor

If you also wish to ensure that each addon adheres to a specific Addon type, you can implement the following validation code:

type Addon = {
  name: string
  // other details
}

// An error will be triggered if any of your addons do not match the Plugin type
export const _addonsTypeCheck: {
  [key: string]: Addon
} = addons

Answer №2

Consider utilizing the enum method to achieve this:

types.d.ts

export enum PluginNames {
   PluginA = 'pluginA',
   PluginB = 'pluginB',
   PluginC = 'pluginC',
}

interface Plugin {
   name: PluginNames
}

src/plugins/pluginA/index.ts

const pluginA: Plugin = {
  name: PluginNames.PluginA
}

export default pluginA

src/plugins/index.ts

import pluginA from "./pluginA"
import pluginB from "./pluginB"
...

export default {
  [PluginNames.PluginA]: pluginA
  [PluginNames.PluginB]: pluginB
  ...
}

You can also accomplish the same outcome by using type unions:

interface Plugin {
   name: 'pluginA' | 'pluginB' | 'pluginC'
}

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

Troubleshooting a Missing Angular (8) Pipe Error in Your Ionic 4 Application

Despite seeing similar questions posted here, none have provided a solution to my issue. I believe I am implementing it correctly, but clearly something is not right. In the app I'm developing with Ionic 4, I need to add a key to a URL in a gallery. ...

Creating a velocity gauge style graph in Angular 4: A step-by-step guide

Hello, I'm currently working on building a speedtest-inspired application. While everything else is going smoothly, I'm struggling to incorporate a speedometer-like chart in Angular 2/4. Despite searching extensively, I've only come across J ...

Upgrading from Angular 5 to 6: Embracing the RxJS Changes without the crutch of rxjs

Currently, I am facing the challenging task of migrating a project from Angular 5.2.11 to version 6.0.0. The main issue I'm encountering is with RxJS 6 (which is essential for Angular versions above 6). Here's an example of one of the errors that ...

What is the method for determining the type of search results returned by Algolia?

My connection between firestore and algoliasearch is working well. I am implementing it with the help of typescript in nextjs. I am attempting to fetch the results using the following code snippet products = index.search(name).then(({hits}) => { ret ...

Guide on utilizing async/await in .ts files (Encountering error message: "async functions can only be used when targeting ECMAScript 6 or above")

Initially, my project consisted of only app.js using ExpressJS as the main file with numerous lines of code. My development manager instructed me to refactor the code and move some functions into a separate .ts file (transition from JavaScript to TypeScrip ...

Impact when returning a React.FC Component

Using React, I have encountered a challenge with my site: I have a function that generates a Card component displaying information about my store's products (#1). To display this on the screen, I map through the array returned by the backend and pass ...

Trouble arises when attempting to import React JSX project/modules from npm into an AngularJS TypeScript module

In the process of developing a proof-of-concept React framework/library, I aim to create a versatile solution that can be utilized in both React and AngularJS applications. To achieve this goal, I have initiated two separate projects: - sample-react-frame ...

Are you harnessing the power of Ant Design's carousel next and previous pane methods with Typescript?

Currently, I have integrated Ant Design into my application as the design framework. One of the components it offers is the Carousel, which provides two methods for switching panes within the carousel. If you are interested in utilizing this feature using ...

Troubleshooting issue with loading local json files in Ionic 3 on Android device

Recently, I've been exploring the process of loading a local JSON data file in my project. I have stored my .json files in the /src/assets/data directory and here is the provider code snippet that I am utilizing: import { Injectable } from '@ang ...

The type declaration for the Storage.prototype.setObject method

I'm facing a challenge in creating a d.ts file for the given DOM feature. Storage.prototype.setObject = function(key:string, value:any) { this.setItem(key, JSON.stringify(value)); } Storage.prototype.getObject = function(key:string) { var va ...

Deactivate user input depending on a certain requirement

Greetings everyone, I am currently working with the following code snippet: <table class="details-table" *ngIf="peop && peopMetadata"> <tr *ngFor="let attribute of peopMetadata.Attributes"> <td class="details-property"&g ...

The function cannot be invoked. The 'Boolean' type does not have any call signatures. An error has occurred in the computed property of Vue3

Currently, I am working on creating a computed property that checks if an item is in the array. The function I have created returns a boolean value and takes one parameter, which is the item to be checked. isSelected: function (item: MediaGalleryItemTypes) ...

Can the 'this' keyword be used to declare the type in TypeScript in this manner?

For instance: // ===== Declaration ===== // class A { CONSTANTS_TYPE: { [key: string]: [any] } CONSTANTS: { [key in keyof this['CONSTANTS_TYPE']]: key } bar<T extends keyof this['CONSTANTS_TYPE'] | string>( type: T, ...

The issue arises when Jest ceases to function properly once the "type": "module" is configured in the tsconfig.json file

I have encountered an issue while using jest for unit testing in typescript. When I set "type": "module" in the tsconfig.json file, my app runs perfectly fine but jest stops working and displays a "ReferenceError: require is not defined". const { pathsToMo ...

Creating a function that utilizes a default argument derived from a separate argument

Consider this JavaScript function: function foo({ a, b, c = a + b }) { return c * 2; } When attempting to add type annotations in TypeScript like so: function foo({ a, b, c = a + b }: { a?: number, b?: number, c: number }): number { return c * 2; } ...

What are some characteristics I can examine in TypeScript?

My understanding of how property checking works in TypeScript was put to the test recently. I noticed that in a specific example, checking for .bold worked fine, but when trying to check for .type, I ran into some confusion. type CustomText = { bold: ...

Triggering an event through a shared messaging service to update the content of a component

I'm looking for a simple example that will help me understand how I can change the message displayed in my component. I want to trigger a confirmation box with *ngIf and once I confirm the change, I want the original message to be replaced with a new ...

Next.js Enhanced with Google Analytics

I've been experimenting with integrating Google Analytics into Next.js, following a tutorial on YouTube - https://www.youtube.com/watch?v=lMSBNBDjaH8 Following the instructions in the video, I added these two scripts in _document.js: <script async ...

Mastering the usage of TypeScript union types with distinct internals

It's frustrating that in the example below, I encounter a typescript error despite the fact that the error is not logically possible to occur in the code. The function receives either Category1 or Category2 and when it returns the ComputedCategory, bo ...

"Sequencing http.get requests in Angular 2 using

In my service, I have a series of http.get requests structured as follows: constructor(private http:Http) {} getDetails(sysID:string){ var details; this.http.get('https://blahURL').map(res => res.json().filter(f => f.id == another.id)[0] ...