Discover the keys of a record using Typescript within a function

I am looking to limit the parameter that can be passed when executing the next function to only the keys of the object provided in createTransitions. This limitation should be applicable across all transitions, as each transition may have different keys.

type Context = { [key: string]: any };

type Action<T extends { [K in keyof T]: any }> = (
  context: Context,
  next: (state?: keyof T) => void,
) => Promise<any> | any;

type Transitions<T extends { [K in keyof T]: any }> = {
  [K in keyof T]: Action<T>;
};

export function createTransitions<T extends { [K in keyof T]: Action<T> }>(
  transitions: T,
): Transitions<T> {
  return transitions;
}

const transitions = createTransitions({
  trans1: (context) => {
    context.payload = context.request.json();
  },
  trans2: (context, next) => {
    if () {
      next("trans3"); // I want this to be valid
      return;
    }

    next("error"); // I want this to be valid
  },
  trans3: (context) => {
    next("any"); // I want this to be invalid
  },
  error: (context) => {
    //
  },
});

Find the complete code here: Codesandbox

Answer №1

It appears that the issue arises when the function is generic in Type T, an object type with keys of importance. The compiler struggles to infer the self-referencing context needed because it assumes Type T depends on the type of 'next' for each property, while 'next''s callback parameter relies contextually on Type T. The fact that only the keys of Type T need consideration is not apparent to the compiler, resulting in failed inference. There is an ongoing issue related to inferring generic type arguments and callback parameters contextually in cases of perceived, yet non-existent circularity; you can refer to microsoft/TypeScript#47599 for more information. Until a significant change occurs regarding this issue, we have to find an alternate approach.

The focus here seems to be less on the object type itself and more on its keys. To address this, let's make the function generic in Key K, representing the union of keys within Type T. This leads us to:

type Action<K extends PropertyKey> = (
    context: any,
    next: (state?: K) => void,
) => Promise<any> | any;

export function createTransitions<K extends PropertyKey>(
    transitions: Record<K, Action<K>>
) {
    return transitions;
}

This code essentially mirrors yours, but replaces 'keyof T' with 'K'. Upon testing:

const transitions = createTransitions({
    trans1: (context) => {
        context.payload = context.request.json();
    },
    trans2: (context, next) => {
        if (Math.random() < 0.5) {
            next("trans3"); // valid
            return;
        }

        next("error"); // valid
    },
    trans3: (context, next) => {
        next("any"); // error!
    },
    error: (context) => {
        //
    },
});

The code works as expected. The compiler efficiently infers Key K from input keys and provides contextual typing for 'next', bypassing circular inference issues by not requiring knowledge of transition properties to determine Key K.

Link to Playground Code

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

Ways to modify a number in grafbase

When attempting to update an integer in grafbase, I encounter the error "Expected an Object for". The issue arises during the update process even though everything appears to be correctly set up. Here is the mutation code: export const updatePetMutation = ...

Detecting the check status of a checkbox in react native: a complete guide

I'm currently working on a scenario where I need to implement logic for checking or unchecking a checkbox in React Native. If the checkbox is checked, I want to print one string, and if it's unchecked, I want to print something else. How can I ac ...

Save geometric shapes data in the SQLite database

How do I go about storing polygon data in an SQLite database? Important: I am utilizing the Cordova plugin. polygon: Point[]; interface Point { x: number; y: number; } https://i.sstatic.net/5EYf2.png ...

Encountering TypeScript errors when trying to reference Angular2 within a Gulp setup

The issue at hand is: [11:16:06] TypeScript: 103 semantic errors [11:16:06] TypeScript: emit succeeded (with errors) I am currently using node v5.7.0 and npm 3.6.0 gulp -v: [11:26:58] Requiring external module babel-register [11:26:58] CLI version 3.9 ...

"Exploring the world of 3rd party libraries in Angular2 with Typescript and Webpack

I've begun working on a fantastic seed project that can be found at: https://github.com/AngularClass/angular2-webpack-starter However, I've encountered an issue with integrating third-party modules. Can anyone offer guidance on how to properly a ...

Can you demonstrate how to display the chosen toggle button within Angular 4 alongside the remaining options?

I have created a custom toggle button component in Angular that I can reuse in different parts of my application. However, I am facing an issue that I need help resolving. I want to display only the selected toggle button and hide the others. When I click ...

What is preventing React CLI from installing the template as TypeScript?

When I run npm init react-app new-app --template typescript, it only generates a Javascript template project instead of a Typescript one. How can I create a Typescript project using the CLI? Current Node JS version: 15.9.0 NPM version: 7.0.15 ...

My customized mat-error seems to be malfunctioning. Does anyone have any insight as to why?

Encountering an issue where the mat-error is not functioning as intended. A custom component was created to manage errors, but it is not behaving correctly upon rendering. Here is the relevant page code: <mat-form-field appearance="outline"> < ...

The readiness status of the mongoose connection is resulting in a TypeError: Unable to access undefined properties (reading 'readyState')

I've been utilizing Mongo Memory Server for my unit tests successfully, but all of a sudden mongoose.connection is returning as undefined. This has left me completely baffled! I would have anticipated readyState to at least be 0. import * as mongoose ...

Tips for accessing the PR number in a Node.js GitHub Probot listening for the `pull_request` event

I've recently developed a GitHub probot application using nodejs and typescript. Currently, I have set up an event listener for the pull_request event. How can I extract the pr_number from the context object within the probot? The snippet below is fr ...

Turn off page animation for a specific page in Ionic 4

While I know that disabling page transitions in the app.module.ts file using IonicModule.forRoot({animated: false}) will turn off transitions and animations for the entire app, I am looking for a way to only disable the page transition for a particular p ...

In Angular, a variable that is exported from a module may not be accessible within a class function

Within my Angular project, I have a service file where I export a variable to be used in another file (component.ts). Interestingly, when I access the value of this variable outside of the class, everything works as expected. However, if I try to access i ...

How many times does the CatchError function in Angular 6 response interceptor get executed?

While working on my Angular project, I implemented an interceptor to intercept all requests and responses. However, I noticed that the function responsible for validating errors in the responses is being executed 7 times. Upon further investigation, I dis ...

What could be the reason for mat-autocomplete not displaying the loading spinner?

Currently, I am integrating Angular Material into my Angular 11 project. One of the pages includes a mat-autocomplete component where I want to display a loading spinner while waiting for a request. Here is the code snippet I am using: component.ts this. ...

Limit a generic type to only accept literal types

Here I have a question that has two parts: I am curious to know if there is a way in TypeScript where it's possible to restrict a generic to be a specific literal type. What I mean is something like function foo<T is a string literal>(...). Th ...

Retrieve a particular element from an array within a JSON object using Ionic

I am currently facing a challenge in extracting a specific array element from a JSON response that I have retrieved. While I can successfully fetch the entire feed, I am struggling to narrow it down to just one particular element. Here is what my service ...

Injecting a component in Angular 2 using an HTML selector

When I tried to access a component created using a selector within some HTML, I misunderstood the hierarchical provider creation process. I thought providers would look for an existing instance and provide that when injected into another component. In my ...

Using Iframe for WooCommerce integration and implementing Facebook login within an Ionic application

I have created an Ionic application that includes an iframe from a Wordpress website. Here is the code snippet from my home.page.ts file: import { Component } from '@angular/core'; import { DomSanitizer } from "@angular/platform-browser"; @Com ...

Encountering "Unexpected token *" error when using Jest on an import statement

What could be the reason for Jest failing with the error message "Unexpected token *" when encountering a simple import statement? Error log: Admin@Admin-PC MINGW32 /d/project (master) $ npm run test > <a href="/cdn-cgi/l/email-protection" class="__ ...

A function that recursively calls itself and uses a promise for iteration

In my current setup, I have a function called emailIterator that reads messages from a stack and sends them one by one using an emailer. The process is done recursively by calling emailIterator with fewer messages each time as they are sent. import { ema ...