What is a method to limit the keys on a Record type?

How can you restrict keys on an object in TypeScript?

In the code snippet below, I am trying to generate a TypeScript error if any key or value in the object passed to a function is found in my DIMENSIONS object.

I have been able to trigger an error for values, but I haven't had success with restricting keys.

const DIMENSIONS = {
  userType: 1,
  moduleVersion: 2
} as const;

type DimensionKeys = keyof typeof DIMENSIONS;
type DimensionValues = typeof DIMENSIONS[DimensionKeys];
type x = typeof DIMENSIONS;

function process<T extends string, V extends number>(yourDimensions: Record<Exclude<T, DimensionKeys>, Exclude<V,DimensionValues>>){
    return {...yourDimensions, ...DIMENSIONS};
}

// ----------------------------------------------
//             Excellent, carry on
// ----------------------------------------------

const myCorrectDimensions = {
  country: 33
} as const;
const combinedDimensions = process(myCorrectDimensions);

// ----------------------------------------------
//        Oops, that value is reserved
// ----------------------------------------------
const myInvalidValueDimensions = {
  country: 1
} as const;
const badDimensions = process(myInvalidValueDimensions);

// ----------------------------------------------
//        Oops, that key is reserved
// ----------------------------------------------
const myInvalidKeyDimensions = {
  userType: 79
} as const;
const badDimensions2 = process(myInvalidKeyDimensions);

Answer №1

One way to approach this is as follows:

function process<T extends Record<keyof T, number>>(
  yourDimensions: { [K in keyof T]:
    K extends DimensionKeys ? never :
    T[K] extends DimensionValues ? never :
    T[K]
  }) {
  return { ...yourDimensions, ...DIMENSIONS };

In this scenario, we define a single object-like generic type parameter T, which corresponds to the function parameter yourDimensions. It is restricted to be of type Record<keyof T, number>, meaning it can have any keys, but their values must be assignable to number.

The actual type of yourDimensions becomes a mapped type

{[K in keyof T]: K extends DimensionKeys ? never : T[K] extends DimensionValues ? never : T[K]}
. TypeScript infers T as the type of yourDimensions, and then checks it against this mapped type. For each key K from the keys of T, the accepted value type
K extends DimensionKeys ? never : T[K] extends DimensionValues ? never : T[K]
represents a conditional type that resolves to the input property type T[K] only if the input property does not use a key from DimensionKeys or a value from DimensionValues. Otherwise, it evaluates to never, resulting in an error because there are no values of type never.

Let's put it to the test:

const example = process({
  a: 3, // valid
  b: 2, // invalid
  moduleVersion: 3 // invalid
})

/* function process<{
    a: 3;
    b: 2;
    moduleVersion: number;
}>(yourDimensions: {
    a: 3;
    b: never;
    moduleVersion: never;
}) */

Here, the compiler infers T to be something like

{a: 3, b: 2, moduleVersion: number}
, so the type of the yourDimensions parameter should be
{a: 3, b: never, moduleVersion: never}
. However, the provided input {a: 3, b: 2, moduleVersion: 3} doesn't match this type. While the a property is acceptable, the other two properties cause errors because they do not evaluate to type never.

Playground link for code testing

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

Issue with beforeSoftRemove hook failing to update database entry in Nest.js and TypeORM

I am looking to incorporate a feature in my Nest.js and TypeORM application where every new record created will automatically include values for createdById, updatedById, and deletedById to track the user actions. Let me provide you with some code snippets ...

The recommended filename in Playwright within a Docker environment is incorrectly configured and automatically defaults to "download."

Trying to use Playwright to download a file and set the filename using download.suggestedFilename(). Code snippet: const downloadPromise = page.waitForEvent('download', {timeout:100000}) await page.keyboard.down('Shift') await p ...

Learn how to mock asynchronous calls in JavaScript unit testing using Jest

I recently transitioned from Java to TypeScript and am trying to find the equivalent of java junit(Mockito) in TypeScript. In junit, we can define the behavior of dependencies and return responses based on test case demands. Is there a similar way to do t ...

The exported instance of sequelize is missing in the Module imports

Good evening! I currently have an express server with a main script that includes the following export: export const sequelize = new Sequelize( 'postgres', config.db_user, config.db_password, { host: 'localhost', port: config ...

The element of type 'any[]' cannot be assigned to type '[id: string]'

My goal is to develop a versatile API structure, where the properties are functions that return Promises. export type API = { [key: string]: <Params extends any[], Response>(...params: Params) => Promise<Response>, } export interface User ...

Errors during TypeScript compilation in Twilio Functions

When I run npx tsc, I encounter the following errors: node_modules/@twilio-labs/serverless-runtime-types/types.d.ts:5:10 - error TS2305: Module '"twilio/lib/rest/Twilio"' does not export 'TwilioClientOptions'. 5 import { Twil ...

Is there a way to implement several filters on an array simultaneously?

Is it possible to filter an array based on both the input text from the "searchTerm" state and the selected option from the dropdown menu? I am currently using react-select for the dropdown functionality. const Positions = ({ positions }: dataProps) => ...

Is there a way to remove a specific column from a table in Angular?

I am looking to develop a dynamic table that allows users to add rows and columns. Additionally, I want the functionality to delete selected columns from the table. You can view my project on Stack Blitz here: https://stackblitz.com/edit/delete-selected-co ...

Converting types to "any" and encountering the error message "There are two distinct types with the same name, but they are not related."

I am encountering some challenges while trying to use an NPM module that I developed along with its Typescript typings in another application. To simplify the examples, I will omit properties that are not relevant to the issue at hand. Within my module&ap ...

The specified property cannot be found within the type 'JSX.IntrinsicElements'. TS2339

Out of the blue, my TypeScript is throwing an error every time I attempt to use header tags in my TSX files. The error message reads: Property 'h1' does not exist on type 'JSX.IntrinsicElements'. TS2339 It seems to accept all other ta ...

Angular: Enable function to await Observable completion before returning result

I require assistance with the user function below: getUser(uuid: string): Observable<WowUserDataModel> { let user: WowUserDataModel = { login: null, userUuid: uuid, firstName: null, lastName: null, displayName: nul ...

When verifying for null in an instance method, an error of "Potential null object" is encountered

If I create an instance function that checks for a non-null property in TypeScript, I encounter an error stating 'Object possibly null' when using the function in a conditional statement. However, if I directly check for null, the error does not ...

What is the best way to set up an endpoint in Angular for image uploading?

Using the Kolkov Angular editor in my Angular application, I have successfully created a rich text editor. Currently, I am looking to upload images from the editor to the server. I already have a function in place that takes a file as an argument and send ...

What is the reason for the variance in the inferred generic type parameter between an extended interface and a type alias representing an intersection of interfaces?

Why is the generic type parameter inferred differently in the following toy experiment, depending on whether the template is instantiated with an extended type or with an intersected type? This experiment has been simplified from a real-world example. inte ...

Find the variance between today's date and a selected date, then initiate the timer based on this variance

I have a grid containing data. I utilize the loadGrid() function to preload data or conditions before the grid finishes loading. When the active state is set to 1, my intention is to initiate a timer by calculating the difference between the current date ...

What is the trick to make the "@" alias function in a Typescript ESM project?

My current challenge involves running a script using ESM: ts-node --esm -r tsconfig-paths/register -T src/server/api/jobs/index.ts Despite my efforts, the script seems unable to handle imports like import '@/server/init.ts': CustomError: Cannot ...

Simulating a winston logger in jest testing

I am pondering how to create mock transports for the File module within the Winston node package. While utilizing Jest, the __mocks__/winston.ts file is automatically loaded. My dilemma lies in the fact that I am unable to mock it due to the presence of th ...

Expanding superagent functionality through proxy integration

I am facing an issue where I cannot find any TypeScript definitions for the module superagent-proxy. This leads to errors when compiling my TypeScript application to JavaScript, specifically: ts: Property 'proxy' does not exist on type 'S ...

Attaching dynamic data to a specific element within an array

I have successfully created a demo where elements can be dropped into a specific area and their top and left values are displayed. I have also added functionality to remove dropped items and move them between different blocks. However, I am encountering so ...

Struggling to containerize my basic typescript web application

Attempting to launch a Typescript project in Docker has been quite the challenge for me. I followed a tutorial to create the Docker file but upon execution, I encountered this error message: /usr/local/bin/docker-entrypoint.sh: 8: exec: .: Permission den ...