Home > Blockchain >  Defining and using a key-value list of generics
Defining and using a key-value list of generics

Time:02-13

I'm sure I'm missing a key concept here but here's what I'm trying to do.

These are the definitions:

export interface ActionError {
  message: string;
}

export interface Request<Payload> {
  payload: Payload;
}

export interface Response<Payload> {
  send: (response?: Payload) => void;
  error: (error?: ActionError) => void;
}

export interface Action<RequestPayload, ResponsePayload> {
  (request: Request<RequestPayload>, response: Response<ResponsePayload>): void;
}

export interface Actions {
  [key: string]: Action<unknown, unknown>;
}

Basically, I'm trying to create a key-value list of Actions that can work with any type that can be passed to a function like below:

const processActions = (actions: Actions) => {
  // Removed for brevity
};

Then I create a key-value object of actions:

const sampleActions = {
  'hello-world': async (
    request: Request<{ input: string }>,
    response: Response<{ output: number }>
  ) => {
    response.send({ output: 10 });
  },
};

And when I call processActions with sampleActions:

processActions(sampleActions) I get this error:

Argument of type '{ "hello-world": (request: Request<{    input: string;}>, response: Response<{    output: number;}>) => Promise<void>; }' is not assignable to parameter of type 'Actions'.
  Property '"hello-world"' is incompatible with index signature.
    Type '(request: Request<{    input: string;}>, response: Response<{    output: number;}>) => Promise<void>' is not assignable to type 'Action<unknown, unknown>'.
      Types of parameters 'request' and 'request' are incompatible.
        Type 'Request<unknown>' is not assignable to type 'Request<{ input: string; }>'.
          Type 'unknown' is not assignable to type '{ input: string; }'

So what am I doing wrong and how can I make this work with any generic type parameter?

CodePudding user response:

My understanding is that the error is simply due to function parameter bivariance. You can satisfy the compiler (and potentially infer a narrowed return type from the processing function) by using a generic for the actions parameter, constrained by a type that uses any for the payloads instead of unknown, as shown in the example below.

Whether any or unknown is used, you still won't have information about the actual types required by the actions' payloads within the processing function. This is simply the nature of the pattern you're using.

TS Playground

export interface ActionError {
  message: string;
}

export interface ActionRequest<Payload> {
  payload: Payload;
}

export interface ActionResponse<Payload> {
  send: (response?: Payload) => void;
  error: (error?: ActionError) => void;
}

export interface Action<ReqPayload, ResPayload> {
  (request: ActionRequest<ReqPayload>, response: ActionResponse<ResPayload>): void;
}

function processActions <Actions extends Record<string, Action<any, any>>>(actions: Actions) {
  // Removed for brevity
}

const sampleActions = {
  'hello-world': async (
    request: ActionRequest<{ input: string }>,
    response: ActionResponse<{ output: number }>,
  ) => {
    response.send({ output: 10 });
  },
};

processActions(sampleActions); // ok, hover in playground link to see inferred generics

  • Related