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:
operations
andCallbacks
type that will typecheck my callback functions based on the structure of operations
?You can use the satisfies
operator 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:
const operations = {
list: {
request: "a",
response: "b",
},
get: {
request: "a",
response: "b",
},
} as const satisfies Operations;
If the above compiles then you know that you're okay. Otherwise you get the errors you expect:
const badOperations = {
list: {
request: "a",
response: "b",
},
get: { // error!
//~ <-- Property 'response' is missing
request: "a",
},
} as const satisfies Operations;
And the type typeof operations
is still exactly as detailed as you need it to be:
type Ops = typeof operations;
/* type Ops = {
readonly list: {
readonly request: "a";
readonly response: "b";
};
readonly get: {
readonly request: "a";
readonly response: "b";
};
} */