Skip to content

Commit

Permalink
feat: add Prune<T,N = NotIncluded>
Browse files Browse the repository at this point in the history
  • Loading branch information
ashgw committed Aug 11, 2024
1 parent 5080ab2 commit ef002fa
Show file tree
Hide file tree
Showing 2 changed files with 421 additions and 0 deletions.
230 changes: 230 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2024,3 +2024,233 @@ export type DeepNotRequired<T> = T extends UnknownFunction
: {
[K in Keys<T>]+?: IfExtends<T[K], unknown, DeepNotRequired<T[K]>, T[K]>;
};

/**
* `NotIncluded` is a utility type that represents a value that should not be included
* in the final type. It is primarily used within the `Prune` type to exclude certain
* properties from the resulting object type.
*
* @remarks
* The `NotIncluded` type is typically used in conditional types to exclude specific
* branches of the type structure. When a condition matches, `NotIncluded` is applied
* to those properties that should be omitted from the final type structure.
*
* @example
* ```ts
* type MyType = IfEquals<'foo', 'bar', string, NotIncluded>; // Result: NotIncluded
* ```
*
* In the context of the `Prune` type, `NotIncluded` helps in filtering out properties
* from deeply nested structures where certain conditions do not hold.
*/
export type NotIncluded = NewType<'NotIncluded', string>;

/**
* `Prune<T, N = NotIncluded>` is a utility type that recursively removes properties of
* type `N` (defaulting to `NotIncluded`) from the object type `T`. This type is useful
* for filtering out unwanted or excluded properties in complex, deeply nested object
* structures.
*
* @remarks
* `Prune` operates by traversing the entire structure of the object type `T` and omitting
* any property that matches the type `N`. This makes it useful when you
* need to clean up or transform types where certain properties should be excluded based
* on specific conditions.
*
* @example
* Consider the following example where `Prune` is used to exclude properties marked as `NotIncluded`:
* ```ts
* type OrderData = Prune<
UserOrderDetails<
OrderStatus.DELIVERED,
UserActionType.PLACE_ORDER,
ProductType.PHYSICAL,
PaymentMethod.PAYPAL
>
>;
* ```
* Where `UserOrderDetails` is defined as:
* ```ts
export enum OrderStatus {
PENDING = 'PENDING',
SHIPPED = 'SHIPPED',
DELIVERED = 'DELIVERED',
CANCELLED = 'CANCELLED',
RETURNED = 'RETURNED',
}
export enum PaymentMethod {
CREDIT_CARD = 'CREDIT_CARD',
PAYPAL = 'PAYPAL',
BANK_TRANSFER = 'BANK_TRANSFER',
CASH_ON_DELIVERY = 'CASH_ON_DELIVERY',
CREDITS = 'CREDITS',
}
export enum UserActionType {
PLACE_ORDER = 'PLACE_ORDER',
CANCEL_ORDER = 'CANCEL_ORDER',
RETURN_ORDER = 'RETURN_ORDER',
RATE_PRODUCT = 'RATE_PRODUCT',
WRITE_REVIEW = 'WRITE_REVIEW',
}
export enum ProductType {
DIGITAL = 'DIGITAL',
PHYSICAL = 'PHYSICAL',
}
interface ShippingDetails {
address: string;
city: string;
postalCode: string;
country: string;
deliveryDate: Maybe<string>;
}
type ProductPricing<T extends ProductType> = {
basePrice: number;
discount: number;
finalPrice: number;
inCountryDiscount: IfEquals<T, ProductType.PHYSICAL, number, NotIncluded>;
};
type ProductDetails<T extends ProductType> = {
type: T;
productId: string;
productName: string;
quantity: number;
pricing: ProductPricing<T>;
physicalDescription: IfEquals<
T,
ProductType.DIGITAL,
{
color: string;
size: string;
},
NotIncluded
>;
};
type PaymentBreakdown = {
baseAmount: number;
tax: number;
discount: number;
finalAmount: number;
};
interface PaymentDetails<PM extends PaymentMethod> {
method: PM;
transactionId: Maybe<string>;
amountPaid: number;
breakdown: IfEquals<PM, PaymentMethod.CREDITS, string, PaymentBreakdown>;
}
type UserOrderDetails<
OS extends OrderStatus,
UAT extends UserActionType,
PT extends ProductType,
PM extends PaymentMethod,
> = {
orderId: string;
userId: string;
products: ProductDetails<PT>[];
orderStatus: OS;
shipping: IfEquals<
PT,
ProductType.PHYSICAL,
{
details: IfEquals<
OS,
OrderStatus.SHIPPED | OrderStatus.DELIVERED,
ShippingDetails,
NotIncluded
>;
deliveredOn: IfEquals<OS, OrderStatus.DELIVERED, Date, NotIncluded>;
returnedOn: IfEquals<OS, OrderStatus.RETURNED, Date, NotIncluded>;
},
NotIncluded
>;
payment: PaymentDetails<PM>;
actions: {
type: UAT;
timestamp: number;
metadata: IfEquals<
UAT,
| UserActionType.PLACE_ORDER
| UserActionType.CANCEL_ORDER
| UserActionType.RETURN_ORDER,
{
ipAddress: string;
deviceType: string;
},
NotIncluded
>;
}[];
};
* ```
* ```ts
const testOrderData: OrderData = {
orderStatus: OrderStatus.DELIVERED,
actions: [
{
timestamp: 1663725600000,
// Type 'UserActionType.RATE_PRODUCT' is not assignable to type 'UserActionType.PLACE_ORDER'.ts(2322)
type: UserActionType.PLACE_ORDER,
// Object literal may only specify known properties, and 'metadata' does not exist in type 'OmitExactlyByTypeDeep<{ type: UserActionType.PLACE_ORDER; timestamp: number; metadata: NotIncluded; }, NotIncluded>'.ts(2353)
// metadata: {
// ipAddress: '127.0.0.1',
// deviceType: 'desktop',
// },
},
],
orderId: '123',
userId: 'abc',
products: [
{
// Type 'ProdcutType.DIGITAL' is not assignable to type 'ProdcutType.PHYSICAL'.ts(2322)
// type: ProductType.DIGITAL,
type: ProductType.PHYSICAL,
productId: 'abc',
productName: 'Test Product',
quantity: 1,
pricing: {
basePrice: 10,
discount: 0,
finalPrice: 10,
// If the product type is digital, the 'physicalDescription' property should be omitted
// Object literal may only specify known properties, and 'inCountryDiscount' does not exist in type 'OmitExactlyByTypeDeep<ProductPricing<ProductType.DIGITAL>, NotIncluded>'.ts(2353)
// physicalDescription: {
// color: 'Red',
// size: 'Large',
// },
inCountryDiscount: 87,
},
},
],
payment: {
amountPaid: 10,
// If the PaymentMethod is CREDITS, the 'breakdown' property should be a string, no Breakdown object should exist
// Type '{ baseAmount: number; tax: number; discount: number; finalAmount: number; }' is not assignable to type 'string'.
breakdown: {
baseAmount: 10,
tax: 2,
discount: 0,
finalAmount: 12,
},
// Type 'PaymentMethod.BANK_TRANSFER' is not assignable to type 'PaymentMethod.PAYPAL'.ts(2322)
// method: PaymentMethod.BANK_TRANSFER,
method: PaymentMethod.PAYPAL,
transactionId: '1234567890',
},
shipping: {
deliveredOn: new Date(),
},
};
* ```
*
* In this example, `Prune` removes all properties within `UserOrderDetails`
* that are marked with `NotIncluded`, resulting in a cleaned-up type structure.
*/
export type Prune<T, N = NotIncluded> = OmitExactlyByTypeDeep<T, N>;
Loading

0 comments on commit ef002fa

Please sign in to comment.