I have a data structure that represents my business operations:
const operations = {
list: {
request: "a",
response: "b",
},
get: {
request: "a",
response: "b",
},
} as const;
I want to create a function that will accept callbacks based on the operations above:
type Ops = typeof operations;
type Callbacks = {
[Property in keyof Ops]: (
param: Ops[Property]["request"]
) => Ops[Property]["response"];
};
Now if I want to define my callbacks the compiler will complain if I miss any of them:
const callbacks: Callbacks = {
};
// ^^^--- Type '{}' is missing the following properties from type 'Callbacks': list, get
Now my problem is that I want to create another type so that I can type check the operations object's structure:
interface OperationDescriptor<A, B> {
request: A;
response: B;
}
type Operations = {
[key: string]: OperationDescriptor<any, any>;
};
const operations: Operations = {
list: {
request: "a",
response: "b",
},
get: {
request: "a",
response: "b",
},
} as const; // okay
const badOperations: Operations = {
list: {
request: "a",
response: "b",
},
get: { // error, missing response prop
request: "a",
},
} as const;
but when I do this the compiler will no longer complain because Operations doesn't know about my keys in operations. Is there a way to have my cake and eat it too, eg:
- Define a type that I can use to typecheck the structure of
operationsand - to have a
Callbackstype that will typecheck my callback functions based on the structure ofoperations?
You can use the
satisfiesoperator to check that a value is assignable to a type without widening it to that type. It's made exactly for the situation where a type annotation would forget information you care about:If the above compiles then you know that you're okay. Otherwise you get the errors you expect:
And the type
typeof operationsis still exactly as detailed as you need it to be:Playground link to code