Secure callback dictionaries

I'm currently working on creating a function that accepts a callback name as an argument and returns a wrapped version of the callback.

Below is my initial attempt at solving this:

const callbacks = {
  alpha: (a: number, b: string) => {},
  bravo: (c: boolean, d: object) => {},
  charlie: (e: symbol, f: Date) => {},
} as const;

type CallbackMap = typeof callbacks;

const getWrappedCallback = <K extends keyof CallbackMap>(key: K) => {
  return (...args: Parameters<CallbackMap[K]>) => {
    console.log("Performing an additional preparation step");
    callbacks[key](...args); // Error: A spread argument must either have a tuple type or be passed to a rest parameter.
  };
};

Upon inspection, you'll notice that the error occurs due to the fact that args is being treated as an array instead of a tuple when used in the second spread operation. Is there a way to overcome this issue?

Answer №1

When working with TypeScript, there may be a challenge in understanding the correlation between the type of callbacks[key] and the type of args within the code line callbacks[key](...args). This difficulty arises when using the type Parameters<CallbackMap[K]>, particularly when K is a generic type parameter linked to a conditional type like the Parameters utility type.

The issue at hand mirrors discussions seen in microsoft/TypeScript#30581; although that example focused on non-generic unions, here we deal with a generic conditional type restricted to a union.


A quick fix involves resorting to type assertions to silence compiler warnings:

const getWrappedCallback = <K extends keyof CallbackMap>(key: K) => {
  return (...args: Parameters<CallbackMap[K]>) => {
    console.log("Performing an additional preparation step");
    (callbacks as any)[key](...args);
  };
};

However, this method sacrifices some level of type safety since mistakes wouldn't be detected by the compiler. For greater assurance, a recommended approach similar to handling microsoft/TypeScript#30581 is advised, outlined in microsoft/TypeScript#47109. By explicitly structuring operations via indexed accesses into mapped types, TypeScript can more accurately comprehend the interaction between various types.


To implement this strategy, start by renaming callbacks for clarity before constructing mapped types:

const _callbacks = {
  alpha: (a: number, b: string) => { },
  bravo: (c: boolean, d: object) => { },
  charlie: (e: symbol, f: Date) => { },
} as const;    
type _CallbackMap = typeof _callbacks;

Create two key types from these actions to differentiate parameter lists and return types from callbacks:

type CallParams = { [K in keyof _CallbackMap]: Parameters<_CallbackMap[K]> }
type CallRets = { [K in keyof _CallbackMap]: ReturnType<_CallbackMap[K]> }

Rebuild callbacks based on the mapped types introduced:

type CallbackMap = { [K in keyof _CallbackMap]: (...args: CallParams[K]) => CallRets[K] };
const callbacks: CallbackMap = _callbacks;

The new CallbackMap type aligns closely with _CallbackMap but is written explicitly via a mapped type structure, enhancing comprehension for the compiler:

const getWrappedCallback = <K extends keyof CallbackMap>(key: K) => {
  return (...args: CallParams[K]) => {
    const r = callbacks[key](...args); // no issues
    // const r: CallRets[K]
  };
};

Following these adjustments, compilation occurs without errors as the relationship between args and callbacks[key] is clarified through indexed access types rather than generic conditional ones.

Try out the code in the TypeScript 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

Trouble encountered when utilizing [ngClass] - Error occurred due to changes in expression after it has been checked

I keep encountering an error when attempting to utilize [ngClass] in my Angular project. The specific error message I receive is as follows: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ' ...

Creating an Array of Callbacks in TypeScript

How do you define an array of callback functions in TypeScript? Here's how a single callback function looks like: var callback:(param:string)=>void = function(param:string) {}; To declare an array of callbacks, you might try this: var callback: ...

How to Retrieve an Array from a Promise Using Angular 4 and Typescript

I am encountering difficulties when trying to store data from a returned promise. To verify that the desired value has been returned, I log it in this manner: private fetchData() { this._movieFranchiseService.getHighestGrossingFilmFranchises() ...

Angular 8 issue: undefined value causing 'toString' property read error

Encountering a perplexing error upon loading my Angular application. The app is not utilizing any overly complex features, but it does make use of Angular Animations (BrowserAnimationsModule) and Bootstrap for styling. The source of the error seems to be ...

How to load a PFX certificate from a file in NodeJS

For my current project involving Node.JS and TypeScript, one of the key requirements is to encrypt the payload body using a PFX certificate read from a .pfx file. The certificate I have is named cert1.pfx, and my code necessitates utilizing this certifica ...

Execute xrmquery.update to update the date field in CRM

I'm developing a custom TypeScript page for a scanning module. I need to update a datetime field on a CRM record when a code is scanned. Check out my code snippet below: XrmQuery.update(x => x.cgk_bonuses, this.bonusId(), ...

Breaking news in the world of programming! The @types/tabulator-tables import is causing a

When you install the following packages: npm install tabulator-tables npm install @types/tabulator-tables And then import them like this: import Tabulator from 'tabulator-tables'; You may encounter an error: Module Usage (Error node_modules @ty ...

What is the best approach to creating multiple dropdowns in ant-design with unique options for each?

It seems like I may be overlooking a simple solution here. Ant-Design dropdowns utilize an array of ItemProp objects to show the options, but this restricts me to having only one list of options. const choices: MenuProps['items'] = [ { label: ...

Utilizing event bubbling in Angular: a comprehensive guide

When using Jquery, a single event listener was added to the <ul> element in order to listen for events on the current li by utilizing event bubbling. <ul> <li>a</li> <li>b</li> <li>c</li> <li>d< ...

Got a value of `false` for an attribute `closable` that is not meant to be a

Here's the code snippet I have: import { Modal } from "antd"; import styled from "styled-components"; export const StANTModal = styled(Modal)` & .ant-modal-content { border-radius: 20px; overflow: hidden; } `; And ...

Utilizing the [mat-dialog-close] directive within an Angular dialog component

While attempting to utilize the suggested code in the dialog template for opening a dialog component to either confirm or cancel an action, I encountered an error with the following message. Why did this happen? Property mat-dialog-close is not provided by ...

Combining platform-express and platform-fastify for optimal performance

I am currently working on a NestJS application and my goal is to upload files using the package @types/multer. However, I encountered an issue while following the guidelines from the official documentation: https://i.sstatic.net/JCX1B.png Upon starting ...

Using TypeScript with Redux for Form Validation in FieldArray

My first time implementing a FieldArray from redux-form has been quite a learning experience. The UI functions properly, but there seems to be some performance issues that I need to investigate further. Basically, the concept is to click an ADD button to i ...

The clash between a static property name type and a dynamic property name on an interface is causing

My interface, Item, is quite straightforward and is applied to various objects. This interface mandates that each of these objects must have an assigned itemName property, but they can also include additional properties with dynamic names if necessary. T ...

The parameter type 'string | VNode' does not align with the expected type 'VNode & string' for this argument

Hi there, I might be mistaken but I could really use some help with this issue I'm facing: Argument of type 'string | VNode' is not assignable to parameter of type 'VNode & string'. Type 'string' is not assign ...

Creating a type that can be used with a generic type T along with an array of the same generic type T

I am experimenting with TypeScript for this project type ArrayOrSingleType<T> = T | T[]; interface TestType<T> { a: ArrayOrSingleType<T>; b: (v: ArrayOrSingleType<T>) => void; } const testVariable: TestType<number&g ...

Struggling to get bcrypt.ts to install on Visual Studio Code for password hashing purposes

As part of my current project, I am working on creating a hash function following the guidelines provided by npmjs's bcrypt documentation: var bcrypt = require('bcrypt.ts'); // Synchronous - 10 rounds equal 10 hashes/sec const saltRounds = ...

Extract the Top X elements from a multidimensional array

Consider an Array that consists of nested arrays: [ ["2000-01-01", "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d1a9a8abe091b6bcb0b8bdffb2bebc">[email protected]</a>", 1, 9, 338], ["2000-01-01", "<a href="/ ...

Using the pipe operator in RXJS to transform an Event into a KeyboardEvent

I'm encountering an error when trying to add a keydown event and convert the parameter type from Event to KeyboardEvent as shown below. fromEvent(document, "keydown") .pipe<KeyboardEvent, KeyboardEvent>( filter((event) => even ...

Differentiating between module-level variables and local variables in TypeScript

Check out this module: export module Example{ let client : any; export function myExample(customer: string) { // How can I access the variable "client" at the module level inside this function? // Should I consider using Pascal Ca ...