Utilities
Selecting Handlers by Type
An in-depth guide to the zet utility for selecting handlers based on type.
Selecting Handlers by Type
Workflow Kit uses extensively the zet utility function.
This utility function creates a function that maps an object to the correct handler based on a specific property inside that object.
To illustrate better how it works imagine we had the following types:
interface Person<T extends Vehicle> {
name: string;
age: number;
vehicle: T;
}
type Vehicle = Car | Bike;
interface Car {
details: {
type: "car";
fuel: "electric" | "gas";
model: string;
year: number;
};
performance: {
horsepower: number;
topSpeed: number; // in km/h
};
}
interface Bike {
details: {
type: "bike";
gear: "fixed" | "multi";
brand: string;
};
specifications: {
weight: number; // in kg
frameMaterial: string;
};
}
If we wanted to create a function called generateMessage that receives a person as argument and returns different data depending on the vehicle that the person has we could use the zet function the following way:
import { zet } from "@/shared/lib/zet";
interface Person<T extends Vehicle> {
name: string;
age: number;
vehicle: T;
}
type Vehicle = Car | Bike;
interface Car {
details: {
type: "car";
fuel: "electric" | "gas";
model: string;
year: number;
};
performance: {
horsepower: number;
topSpeed: number; // in km/h
};
}
interface Bike {
details: {
type: "bike";
gear: "fixed" | "multi";
brand: string;
};
specifications: {
weight: number; // in kg
frameMaterial: string;
};
}
interface Zet {
object: Person<Vehicle>;
nested: ["vehicle"];
filter: ["details", "type"];
params: [];
return: string;
}
const dispatch = zet<Zet>(["vehicle"], ["details", "type"], {
car: (person: Person<Car>) => {
return `This person drives a ${person.vehicle.details.fuel} car.`;
},
bike: (person: Person<Bike>) => {
return `This person rides a ${person.vehicle.details.gear} bike.`;
},
});
function generateMessage(person: Person<Vehicle>): string {
return dispatch(person);
}
const personWithCar: Person<Car> = {
name: "John",
age: 25,
vehicle: {
details: {
type: "car",
fuel: "gas",
model: "Toyota Corolla",
year: 2020,
},
performance: {
horsepower: 132,
topSpeed: 180,
},
},
};
console.log(generateMessage(personWithCar)); // This person drives a gas car.
The type that the function receives is an object with the following properties:
object: The input object.nested: The path to the property that contains the union you want to narrow.filter: The path to the key in the union whose value determines which handler to use.params: Extra arguments to pass to the returned function.return: The return type of the returned function.
The function parameters that the function receives are the following:
- The path to the property that contains the union you want to narrow.
- The path to the key in the union whose value determines which handler to use.
- An object where each property points to the handler function that should run.
Refine Utility Type
If Person was not generic, we would need to use the Refine utility type:
import { zet, type Refine } from "@/shared/lib/zet";
interface Person {
name: string;
age: number;
vehicle: Vehicle;
}
type Vehicle = Car | Bike;
interface Car {
details: {
type: "car";
fuel: "electric" | "gas";
model: string;
year: number;
};
performance: {
horsepower: number;
topSpeed: number; // in km/h
};
}
interface Bike {
details: {
type: "bike";
gear: "fixed" | "multi";
brand: string;
};
specifications: {
weight: number; // in kg
frameMaterial: string;
};
}
type PersonWithCar = Refine<{
object: Person;
nested: ["vehicle"];
filter: ["details", "type"];
narrow: "car";
}>;
type PersonWithBike = Refine<{
object: Person;
nested: ["vehicle"];
filter: ["details", "type"];
narrow: "bike";
}>;
interface Zet {
object: Person;
nested: ["vehicle"];
filter: ["details", "type"];
params: [];
return: string;
}
const dispatch = zet<Zet>(["vehicle"], ["details", "type"], {
car(person: PersonWithCar) {
return `This person drives a ${person.vehicle.details.fuel} car.`;
},
bike(person: PersonWithBike) {
return `This person rides a ${person.vehicle.details.gear} bike.`;
},
});
function generateMessage(person: Person): string {
return dispatch(person);
}
const personWithCar: PersonWithCar = {
name: "John",
age: 25,
vehicle: {
details: {
type: "car",
fuel: "gas",
model: "Toyota Corolla",
year: 2020,
},
performance: {
horsepower: 132,
topSpeed: 180,
},
},
};
console.log(generateMessage(personWithCar)); // This person drives a gas car.