List of property names transformed into actual properties

I have a collection of functions stored as key-value pairs that can be utilized by a "processor".

const fns = {
  foo: () => ({ some: "data", for: "foo" }),
  bar: () => ({ and: "data", for: "bar" }),
  baz: () => ({ baz: "also", is: "here" }),
};

Next, I define an interface outlining the requirements of a "processor":

interface NeedyProcessor {
  needed: Array<keyof typeof fns>;
  // Question 1: Can needsMet be typed more strongly?
  process: (needsMet: any) => any;
}

The objective is to specify the required function properties from `fns` that the processor needs to operate.

For example, if `needed` includes `["foo"]`, it implies that the `processor` will receive an object `needsMet` with a single property, `foo`, holding the value of `foo` from `fns`.

However, inadequate typing leads to potential errors like the uncaught issue in Typescript:

const broken: NeedyProcessor = {
  needed: ["foo", "baz"],
  process: (needsMet) => ({
    fooResult: needsMet.foo(),
    // Question 1 cont: So that this call would give an error in the IDE
    barResult: needsMet.bar(), // Runtime error! bar won't exist
    bazResult: needsMet.baz(),
  }),
};

To illustrate further, here's a scenario without runtime errors:

const working: NeedyProcessor = {
  needed: ["bar"],
  process: (needsMet) => ({
    barResult: needsMet.bar(),
  }),
};

A helper function will facilitate making the call. It's crucial to include only the necessary `fns` for `process`:

function callProcessor(spec: NeedyProcessor) {
  // Question 2: Could this any be typed more strongly? Not as important as question 1 though
  const needsMet: any = {}

  spec.needed.forEach(x => needsMet[x] = fns[x])

  return spec.process(needsMet);
}

One scenario will succeed while the other fails:

console.log("working", callProcessor(working));
console.log("broken", callProcessor(broken));

Playground Link

Answer №1

You won't find a perfect particular type that suits your requirements. Theoretically, consider NeedyProcessor as a potential union of all acceptable input types, like so:

type NuttyInstructor =
  { needed: []; process: (needsFulfilled: Pick<Fns, never>) => any; } |
  { needed: ["baz"]; process: (needsFulfilled: Pick<Fns, "baz">) => any; } |
  // Other definitions omitted for brevity...

However, this approach doesn't handle large fns efficiently, and since it's not a differentiated union, it won't function as expected...the process callback parameter cannot be contextually typed:

const shouldWorkButDoesNot: NuttyInstructor = {
  needed: ["bar"],
  process: (needsFulfilled) => ({ 
    barResult: needsFulfilled.bar(),
  }),
};

Unfortunately, it falls short.


Instead, you could make NeedyProcessor<K> generic with the type K of elements in needed. To automate inferring K, consider crafting a helper function. Here's an example:

interface NeedyProcessor<K extends keyof Fns> {
  needed: Array<K>;
  process: (needsFulfilled: Pick<Fns, K>) => any;
}

const needyHelper = <K extends keyof Fns>(
  np: NeedyProcessor<K>) => np;

Now, instead of declaring const v: NeedyProcessor = {...}, use const v = needyHelper({...}):

const working = needyHelper({
  needed: ["bar"],
  process: (needsFulfilled) => ({
    barResult: needsFulfilled.bar(),
  }),
});
// const working: NeedyProcessor<"bar">

Ensure it catches errors:

const broken = needyHelper({
  needed: ["foo", "baz"],
  process: (needsFulfilled) => ({
    fooResult: needsFulfilled.foo(),
    bazResult: needsFulfilled.baz(),
  }),
})

Looks promising!


Answer №2

To maximize efficiency, consider utilizing generics instead of the required attribute. Here is a potential implementation:

const operations = {
  action1: () => ({ details: "data", related: "action1" }),
  action2: () => ({ information: "details", for: "action2" }),
  action3: () => ({ specific: "content", provided: "here" }),
};

interface RequiredProcessor<T extends keyof typeof operations> {
  process: (requirementsMet: Pick<typeof operations, T>) => unknown;
}

const incomplete: RequiredProcessor<"action1" | "action3"> = {
  process: (requirementsMet) => ({
    resultForAction1: requirementsMet.action1(),
    resultForAction2: requirementsMet.action2(), // Potential runtime error as action2 may not exist
    resultForAction3: requirementsMet.action3(),
  }),
};

An alternative approach could involve using infer to simplify the solution further.

=========

Update:

Upon further consideration, a revised strategy has been devised to maintain the list of required values at runtime.

type RequiredActions = "action1" | "action2" | "action3"

const actions = {
  action1: () => ({ details: "data", related: "action1" }),
  action2: () => ({ information: "details", for: "action2" }),
  action3: () => ({ specific: "content", provided: "here" }),
};

interface RequiredProcessor<T extends readonly RequiredActions[]> {
  required: T,
  process: (requirementsMet: Pick<typeof actions, T[number]>) => any; 
}

const incomplete: RequiredProcessor<["action1", "action3"] > = {
  required: ["action1", "action3"],
  process: (requirementsMet) => ({
    resultForAction1: requirementsMet.action1(),
    resultForAction2: requirementsMet.action2(), // Potential runtime error as action2 may not exist
    resultForAction3: requirementsMet.action3(),
  }),
};

The caveat is that both the array of required actions and the required field need to be specified as generic types. Alternatively, you can create an immutable array using as const and use it in both places:

const neededActions = ["action1", "action3"] as const
const incomplete: RequiredProcessor<typeof neededActions> = {
  required: neededActions,
  process: (requirementsMet) => ({
    resultForAction1: requirementsMet.action1(),
    resultForAction2: requirementsMet.action2(), // Potential runtime error as action2 may not exist
    resultForAction3: requirementsMet.action3(),
  }),
};

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

New Requirement for Angular Service: Subclass Constructor Must Be Provided or Unable to Resolve all Parameters for ClassName (?)

During a recent project, I encountered an issue while working on several services that all extend a base Service class. The base class requires a constructor parameter of HttpClient. When setting up the subclass with autocomplete, I noticed that their con ...

Trouble retrieving query parameters from a URL while trying to access URL parameters from a module

I am currently learning angular and facing a small problem that I'm unsure how to solve. My module looks like this: const hostHandler = setContext((operation: any, context: any) => ({ headers: { ...context?.headers, 'X-Location-Hostn ...

Nest is unable to resolve DataSource in the dependency injection

I've encountered an issue with dependencies while working on my NestJS project. When I try to launch the app, the compiler throws this error at me: [Nest] 16004 - 09.04.2022, 16:14:46 ERROR [ExceptionHandler] Nest can't resolve dependencies of ...

Is it possible to enable typescript to build in watch mode with eslint integrated?

Can this be achieved without relying on webpack or other bundlers? Alternatively, is the only solution to have two separate consoles - one for building and another for linting? ...

Creating Typescript packages that allow users to import the dist folder by using the package name

I am currently working on a TypeScript package that includes declarations to be imported and utilized by users. However, I have encountered an issue where upon publishing the package, it cannot be imported using the standard @scope/package-name format. I ...

Can one bring in a JavaScript function using webpack?

I have a unique JS library called: say-my-greeting.js function SayMyGreeting (greeting) { alert(greeting); } Now I want to incorporate this function in another (.ts) file; special-class.ts import SayMyGreeting from './say-my-greeting.js' ex ...

Mapping object array values to the same key in Angular is a common task that can

Recently, I encountered an object that looks like this: const product = { name: 'watch', color: ['brown', 'white'] } Here's what I'm aiming for: I want to transform this object into the following format: name: ...

Sign up for the completion event within the datetime picker feature in Ionic 2

How can I subscribe to the "done" event in Ionic2, where I want to trigger a function after selecting a date? <ion-icon class="moreicon" name="funnel"> <ion-datetime type="button" [(ngModel)]="myDate" (click)="getData()"></ion-datetime> ...

What is the process for generating an index.d.ts file within a yarn package?

I'm facing an issue with creating the index.d.ts file for my yarn package. Here is my configuration in tsconfig.json: { "include": ["src/**/*"], "exclude": ["node_modules", "**/*.spec.ts"], " ...

Error message: "The property is not found within the specified type when using the OR operator with

Within my Angular component, I am faced with a challenge involving an Input that can be one of two types. @Input() profile: UserProfileDetails | BusinessProfileDetails; The structure of the profile template is straightforward and I want to avoid duplicati ...

Saving the current state of a member variable within an Angular 2 class

export class RSDLeadsComponent implements OnInit{ templateModel:RSDLeads = { "excludedRealStateDomains": [{"domain":""}], "leadAllocationConfigNotEditables": [{"attributeName":""}] }; oldResponse:any; constructor(private la ...

Typescript - Troubleshooting undefined error with static variables

My node API app is developed using express and typescript. The static variable of the Configuration Class is initialized with required configuration before starting the server. However, when I try to use this static variable in a separate TypeScript class ...

Transforming a mongodb operation into an asynchronous function using await and async syntax

After calling the function to retrieve data from MongoDB, an undefined error occurs. It is suspected that converting the function to an async/await function may resolve this issue. However, there is uncertainty on how to make this conversion without disrup ...

Angular Error TS2554: Received x arguments instead of the expected 0 on piped operators

I encountered an issue with Error TS2554: Expected 0 arguments, but got 4 when dealing with the observable getHappyDays(). The getHappyDays() Observable returns either Observable<HttpResponse<IHappyDays>> or Observable<HttpErrorResponse> ...

What is the process for utilizing a user AD account with SecretClient in a node/TypeScript environment?

Currently, I am faced with authentication challenges while attempting to utilize the Azure Key Vault Secret client library (https://www.npmjs.com/package/@azure/keyvault-secrets). Most of the examples provided involve service principal usage, but my requ ...

Restricting union types by a specific property

I'm facing an issue when attempting to narrow down a type based on a property. To explain it better, here's a simplified version in code: type User = { id: number; name: string; } type CreateUser = { name?: string; } const user: User | Cr ...

What is the method for transmitting a URL API from an ASP.NET Core server to my Angular 2 single application?

Is there a way to securely share the url of the web api, which is hosted on a different server with a different domain, from my asp net core server to my client angular2? Currently, I am storing my settings in a typescript config file within my angular2 ap ...

Exploring the world of Typescript class decorators and accessing its content from within

Greetings, I am searching for a method to define a class in TypeScript and retrieve its value from within the parent. abstract class Base{ fetchCollectionName(): string{ // code here to return child class attribute value } } @Collectio ...

Response type tailored to specific input type

I am working on defining types for a simple function called fun. Below are the interfaces for the input and response: interface Input { test1?: true test2?: true test3?: true } interface Res { test1?: string test2?: string test3?: string } N ...

Differences between `typings install` and `@types` installation

Currently, I am in the process of learning how to integrate Angular into an MVC web server. For guidance, I am referring to this tutorial: After some research and noticing a warning from npm, I learned that typings install is no longer used. Instead, it ...