Is there a way to create a unique set of string values using the keys and values from an object that are produced by a function?

I am currently in the process of defining a new type based on an immutable object, like so:

const Actions = {

  'user.crud': ['user.create', 'user.read', 'user.update', 'user.delete'],

} as const

type ActionsType = keyof typeof Actions | typeof Actions[keyof typeof Actions][number]

The aforementioned approach works effectively and establishes the ActionsType with specific string literals ('user.crud', 'user.create', etc.).

Nevertheless, the simplicity of the Actions object above does not meet my requirements. Ideally, I would like to dynamically generate the Actions using functions. After transitioning the above setup to be generated by a function:

// define a function to create all actions for a given role
function getActions(role: string): Record<string, string[]> {

    return {
        [`${role}.crud`]: [`${role}.create`, `${role}.read`, `${role}.update`, `${role}.delete`],
    }

}

// generate the Actions using a function
const ActionsFromFunction = {

  ...getActions('user'),

} as const

// establish the Actions from an immutable object with values produced by getActions()
type ActionsFromFunctionType = keyof typeof ActionsFromFunction | typeof ActionsFromFunction[keyof typeof ActionsFromFunction][number]

the type ActionsFromFunctionType no longer reflects the string literals but instead is set to: string | number, causing type checks to fail as any string is accepted.

I have created a demonstration of the scenario:

Playground

Is there a method to construct the Actions object through a function while preserving the specific string literals within the type?

Answer №1

To achieve your objective, you will need to utilize TypeScript's Template literal types. These features are not yet available in TypeScript 4.0, but they are expected to be included in the upcoming 4.1 version.

This example demonstrates how it can be done with TypeScript 4.1:

type CrudOperations<ROLE extends string> = [`${ROLE}.create`, `${ROLE}.read`, `${ROLE}.update`, `${ROLE}.delete`];

type GetActionsResult<ROLE extends string> = string extends ROLE // check if we can infer type
  ? { [k: string]: string[] } // if type is not inferable
  : { [K in `${ROLE}.crud`]: CrudOperations<ROLE> };

function getActions<ROLE extends string>(role: ROLE): GetActionsResult<ROLE> {
    return {
      [`${role}.crud`]: [`${role}.create`, `${role}.read`, `${role}.update`, `${role}.delete`]
    } as GetActionsResult<ROLE>;
}

// falls back to { string: string[] } structure
const actions = getActions('admin' as string);

// generate the Actions from a function
const ActionsFromFunction = {
  ...getActions('user'),
  ...getActions('orders'),
}

Playground link

Answer №2

To simplify, you can use getActions without specifying types explicitly:

function getActions<K extends string>(role: K) {
  const actions = {
    [`${role}.crud`]: [`${role}.create`, `${role}.read`, `${role}.update`,
    `${role}.delete`],
  } as const
  return actions as Record<K,typeof actions[K]>
}
Test it:
const ActionsFromFunction = {
  ...getActions('user'),
} 
// { user: readonly ["user.create", "user.read", "user.update", "user.delete"];}

const ActionsFromFunction2 = {
  ...getActions('user' as string),
} 
// { [x: string]: readonly [`${string}.create`, ..., `${string}.delete`]; }

type ActionsFromFunctionType =
  | keyof typeof ActionsFromFunction
  | typeof ActionsFromFunction[keyof typeof ActionsFromFunction][number]
// "user" | "user.create" | "user.read" | "user.update" | "user.delete"

Note that if your role has a union of string type, the accuracy of getActions will be compromised:

const ActionsFromFunction3 = {
  ...getActions('user' as 'user' | 'admin'),
} // { user: ...; admin: ...; }

If necessary, you can make use of a distributed conditional type in getActions:

return actions as K extends any ? Record<K,typeof actions[K]> : never
const ActionsFromFunction3 = {
  ...getActions('user' as 'user' | 'admin'),
}
/* 
| { user: readonly ["user.create", "user.read", "user.update", "user.delete"];} 
| { admin: readonly ["admin.create", "admin.read", "admin.update", "admin.delete"]; } 
*/

Learn more about distributed conditional types.

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

Error: $(...).DataTable function is not recognized - DataTables requires $.noConflict(true) to function properly

Recently I stumbled upon DataTables and decided to experiment with it. My goal for the page is to search and display posts based on user-defined parameters, updating the table content with JSON without having to refresh the entire page. However, I'm e ...

Is it possible to generate a vendor bundle for CSS/Sass in Vue.js CLI 3?

I am currently using @vue/cli 3.x and I made some changes to my vue.config.js. I am looking to have separate CSS files like app.css and vendor.css (compiled from Sass) - similar to how it handles JavaScript. However, I am unsure about the correct configura ...

New messages are revealed as the chat box scrolls down

Whenever a user opens the chatbox or types a message, I want the scroll bar to automatically move down to show the most recent messages. I came across a solution that seems like it will do the trick: The issue is that despite implementing the provided cod ...

Including additional data in JSON objects

For example: I have an object named tempobj with sample data at indexes tempobj[0] and tempobj[1]. Now, I would like to include additional information like name and status in this object. tempobj["info"]["name"] = "title"; tempobj["info"]["id"] = "23243 ...

Received an error message saying "Module 'src/environments/environment' cannot be found" when attempting to execute the ng e2e command

Just starting with Angular and Typescript, I attempted to run e2e testing on my Angular project using the "ng e2e" command, but encountered an error that reads: E/launcher - Error: Error: Cannot find module 'src/environments/environment' I searc ...

The type 'Observable<any>' cannot be assigned to the type 'Observable<T>'

Here is the code I am working with: import {HttpClient} from '@ngular/common/http'; private httpClient: HttpClient; do_request(method: string, url: string, ...

tips for obtaining the highest value among multiple keys within an array

How can I find the maximum value among multiple keys in an array? I previously attempted to find the maximum values for just three keys. getMaxValuefromkeys(values: any[], key1: string, key2: string, key3: string) { var val1 = Math.max.apply(Math, v ...

Watching a service's attribute from within a route in the EmberJS framework

There seems to be a concept that I'm struggling to grasp here. To my understanding, any instance of Ember.object should be able to observe properties on another instance of Ember.object. In my scenario, there is a service, a router, and a component i ...

Dealing with HTML and Escaping Challenges in jQuery Functions

Here is a string I have: var items = "<div class='item'><div class='item-img' style='background-image: url('images.123.jpg')'></div></div>" I am looking to update the inner HTML of a div: $ ...

Three.js emits a selection list

I'm currently working on creating a picking ray to determine if my 3D body in three.js has been clicked. I've tried following some advice from different sources like this post and this thread. This is the code I have so far: function checkClick ...

Which is more efficient in JavaScript: Arrays, Object literals, or JSON for achieving better performance?

I'm facing a tough decision in choosing the most effective option. Currently, I have a simple array set up like this: var array = [ '/index1.html', '/index2.html', '/index3.html' ]; While this array consists ...

Recording setInterval data in the console will display each number leading up to the current count

Currently, I am developing a progress bar that updates based on a counter over time. To achieve this, I opted to utilize a setInterval function which would update the counter every second, subsequently updating the progress bar. However, I encountered an ...

Verify if the array entries match

Within my select element, I populate options based on an array of values. For example: [{ name: 'A', type: 'a', }, { name: 'B', type: 'b', }, { name: 'B', type: 'b', }, { name: &apos ...

What is the best way to make two buttons align next to each other in a stylish and elegant manner

Currently, I am diving into the world of glamorous, a React component styling module. My challenge lies in styling two buttons: Add and Clear. The goal is to have these buttons on the same row with the Clear button positioned on the left and the Add button ...

I am attempting to create a multi-line tooltip for the mat-icon without displaying " " in the tooltip

I attempted to create a multiline tooltip using the example below. However, the \n is showing up in the tooltip. I am looking to add a line break like we would with an HTML tooltip. Check out the code here. ...

Best practices for utilizing JSON data to maximize its value

When using node.js version 6.10 console.log("retrieved data1 " + body); // returns: retrieved data1 "{'response' : 'Not latest version of file, update not performed'}" I am attempting to extract the Not latest version of file, update ...

What is the technique for retrieving variable types from beyond the function's scope?

I am facing a challenge with a function where I have declared a variable with a somewhat complex structure: export function foo() { const myVar = { // some properties with complex types here... } // Do something with `myVar` } Now, I ...

Displaying and concealing a subcomponent within a parent element for a set duration of time

I have a notification component that is displayed as a child component in the parent component after a button click. It automatically hides after a certain number of seconds. Here is the code I have developed: MyCode Parent component: <button (click)= ...

How to convert a 4-bit byte array into an integer using JavaScript

Utilizing web sockets, I transmit a 4-bit byte array to my JavaScript library. This byte array signifies a number that I want to retrieve back, all within JavaScript. Examples abound for reading a byte array into a string, but finding examples for the reve ...

Incorporating the Material UI App Bar alongside React Router for an enhanced user

For my web development course project, I am venturing into the world of JavaScript and React to create a website. Utilizing Material UI's appbar for my header design, I have successfully incorporated react router to enable navigation on my site. Howev ...