What steps can be taken to patch a type during the compilation process?

Is it possible to achieve the necessary dynamic typing in TypeScript to establish a component system of this nature?

let block = new Entity();
// block.components === {};
block.has(new Position(0, 0));
// block.components === { position: { ... } }

In this scenario, Entity#components does not feature an index signature, but rather a specific structure where keys correspond to appropriate component types.

Here is a preliminary attempt at implementing this concept:

class Position implements Component {
  name = "position";
  x = 0;
  y = 0;
}

interface Component {
  name: string;
  [key: string]: any;
}

class Entity<T={}> {
  components: T;

  has(component: Component) {
    type ExistingComponents = typeof this.components;
    type NewComponents = { [component.name]: Component };
    this.components[component.name] = component;
    return this as Entity<ExistingComponents & NewComponents>;
  }
}

There are some issues with this implementation:

  • The has method updates the original entity with a revised type instead of directly modifying the existing type.
  • The NewComponents type encounters compilation errors due to its dependence on a runtime property (component.name).

An alternative approach considered involves integrating extension into the components themselves to ensure static names:

class Position implements Component {
  name = "position";
  x = 0;
  y = 0;

  static addTo(entity: Entity) {
    type Components = typeof entity.components & { position: Position };
    entity.components.position = new Position();
    return entity as Entity<Components>;
  }
}

let position = new Position(0, 0);
position.addTo(block);

However, this method seems cumbersome and still fails to address the challenge of redefining the type without creating a new one.

Is there a way to modify the type during compile time using a method call?

Answer №1

To maintain the string literal type representing the component name at compile time, we must make Component generic. By using conditional types effectively, we can achieve the desired outcome:

// T will represent the component name (e.g., 'position')
interface Component<T> {
    name: T;
    [key: string]: any;
}
// Conditional type to extract the component name (e.g., ComponentName<Position> =  'position')
type ComponentName<T extends Component<any>> = T extends Component<infer U> ? U : never;

class Entity<T={}> {
    components: T;
    // TNew represents the added component type 
    // We return Entity with the original T and include a new property of ComponentName<TNew> which will be of type TNew with a mapped type
    has<TNew extends Component<any>>(componentInstance: TNew) : Entity<T & { [ P in ComponentName<TNew>] : TNew }> {
        this.components[componentInstance.name] = componentInstance;
        return this as any;
    }
}

//Example of Usage: 
export class Position implements Component<'position'> {
    readonly name = "position";
    x = 0;
    y = 0;
}


export class Rectangle implements Component<'rectangle'> {
    readonly name = "rectangle";
    x = 0;
    y = 0;
}

let block = new Entity().has(new Position()).has(new Rectangle());

block.components.rectangle //okay
block.components.position // okay

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

Implementing multer diskStorage with Typescript

I'm currently in the process of converting a node.js server to TypeScript. Here is what my function looks like in Node: const storage = multer.diskStorage({ destination: function (req, file, cb) { const dir = './uploads/'; ...

What is the reason behind Lerna consistently releasing all of my packages?

I decided to create a mini project showcasing my "open-sourced" libraries which can be found here. I implemented Lerna to automate the publishing process for these projects. However, I'm encountering an issue where every package gets updated each time ...

How to access custom parameters set in middleware in Next.js server actions?

I'm currently utilizing middleware.ts to intercept and authenticate user requests. However, I want to include data from our database in these authenticated requests. Within my server action code, the structure is as follows: export async function get ...

The current issue I am facing is that the option disabled with value="null" selected is not being shown on the screen. Instead, it should display as "Choose User Types"

https://i.sstatic.net/JqZ7O.png <select class="form-control" id="ddSelectaTopic" onchange="if(this.value==='') {this.style.color='#999'} else {this.style.color='#333'}" [(ngModel)]="us ...

Creating a class dynamically in Angular 2 Typescript based on a property value

How can I dynamically assign classes in HTML based on a property in Angular 2 without using jQuery or Bootstrap? I am trying to open a dropdown list. Here is what I have: <li class="dropdown mega-menu mega-menu-wide" //stuck at adding class of op ...

I possess both a minimum and maximum number; how can I effectively create an array containing n random numbers within

Given a minimum number of 10.5 and a maximum number of 29.75, the task is to generate an array within these two ranges with a specific length denoted by 'n'. While the function for generating the array is provided below, it is important to calcul ...

Angular Observable returning null results

After spending some time on this issue, I am still puzzled as to why I am consistently receiving an empty observable. Service: import { Injectable } from '@angular/core'; import { WebApiService } from './web-api-service'; import { Beha ...

Nexus and GraphQL: The root typing path for the "context" type is not found

I’m currently working on integrating GraphQL into Next.js API routes. For writing the GraphQL schema, I’m utilizing Nexus. Here are the two essential files: context.ts and schema.ts, that help in setting up Nexus development mode. // context.ts import ...

Make sure to implement validations prior to sending back the observable in Angular

Each time the button is clicked and if the modelform is invalid, a notification message should be returned instead of proceeding to create a user (createUser). The process should only proceed with this.accountService.create if there are no form validation ...

Angular service not triggering timer

I've developed a timer service file in Angular for countdown functionality import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class TimerUtilService { initial_module_timer_value = 300; sec ...

What is the significance of the message "JavaScript files do not have any valid rules specified"?

I am working on a React - Typescript project that was created using the following command: create-react-app my-app --scripts-version=react-scripts-ts While it compiles without any issues, I keep receiving a message or warning that says: No valid rules h ...

Guide on creating dynamic route paths for includes within a Pug template

Need help creating a dynamic include For example: h1 include path/#{object} or include path/+{object}+{a:true,b:11} Something similar to the above. If anyone knows how to achieve this using Mixins in pug, please provide an example for include. ...

There seems to be a compatibility issue between Angular 16 and Bootstrap 5 styling

In my angular.json, I have defined my styles in the following way: "styles": [ { "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", "bundleName": "ltrCSS" }, { "input": "node_mod ...

Steps for creating a table with a filter similar to the one shown in the image below

https://i.sstatic.net/zR2UU.png I am unsure how to create two sub-blocks within the Business A Chaud column and Potential Business Column. Thank you! I managed to create a table with input, but I'm struggling to replicate the PUSH & CtoC Column for ...

Issue with Angular 6: Textarea displaying value as Object Object

I have data saved in local storage using JSON.stringify, and I want to display it in a textarea. Here are the relevant code snippets: { "name": "some name" } To retrieve the data, I'm using this: this.mydata = localStorage.getItem('mydata&a ...

The Angular Compiler was identified, however it turned out to be an incorrect class instance

Although this question has been asked before, I have exhausted all possible solutions that were suggested. Unfortunately, I still cannot resolve it on my own. Any assistance would be greatly appreciated. Error: ERROR in ./src/main.ts Module build failed: ...

Mapping arrays from objects using Next.js and Typescript

I am trying to create a mapping for the object below using { ...product.images[0] }, { ...product.type[0] }, and { ...product.productPackages[0] } in my code snippet. This is the object I need to map: export const customImage = [ { status: false, ...

Utilizing string to access property

Is there a better way to access interface/class properties using strings? Consider the following interface: interface Type { nestedProperty: { a: number b: number } } I want to set nested properties using array iteration: let myType: Type = ...

Does the term 'alias' hold a special significance in programming?

Utilizing Angular 2 and Typescript, I have a component with a property defined as follows: alias: string; Attempting to bind this property to an input tag in my template like so: <input class="form-control" type="text" required ...

JSONPath encounters an issue when square brackets are embedded within a string

I am encountering issues with the JSONPath library found at https://github.com/JSONPath-Plus/JSONPath in its latest version. For example: { "firstName": "John", "lastName": "doe", "age": 26, ...