Utility Types
SimpleMap
A simple key-value map with optional values.
type SimpleMap < T > = { [ key : string ] : T | undefined }
Type of values in the map
Basic Usage
With Loading States
import type { SimpleMap } from '@proton/shared/lib/interfaces' ;
const userMap : SimpleMap < User > = {
'1' : { id: '1' , name: 'John' },
'2' : { id: '2' , name: 'Jane' },
'3' : undefined // Optional values allowed
};
LoadingMap
Specialized map for tracking loading states.
type LoadingMap = SimpleMap < boolean >
import type { LoadingMap } from '@proton/shared/lib/interfaces' ;
const loading : LoadingMap = {
fetchUsers: true ,
saveSettings: false ,
deleteItem: true
};
MaybeArray
Represents a value that can be either a single item or an array.
type MaybeArray < T > = T [] | T
Function Parameter
Type Guard
function process ( ids : MaybeArray < string >) {
const idArray = Array . isArray ( ids ) ? ids : [ ids ];
// Process array
}
process ( '123' ); // Single value
process ([ '123' , '456' ]); // Array
Nullable
Makes a type nullable.
type Nullable < T > = T | null
import type { Nullable } from '@proton/shared/lib/interfaces' ;
interface User {
id : string ;
name : string ;
avatar : Nullable < string >; // Can be string or null
}
const user : User = {
id: '1' ,
name: 'John' ,
avatar: null // Valid
};
Optional
Make specific properties optional in an interface.
type Optional < T extends object , K extends keyof T = keyof T > =
Omit < T , K > & Partial < Pick < T , K >>
Make Specific Fields Optional
In Function Parameters
import type { Optional } from '@proton/shared/lib/interfaces' ;
interface User {
id : string ;
name : string ;
email : string ;
avatar : string ;
}
// Make avatar and email optional
type UserInput = Optional < User , 'avatar' | 'email' >;
const input : UserInput = {
id: '1' ,
name: 'John'
// email and avatar are optional
};
RequireOnly
Make entire type partial except for specified required keys.
type RequireOnly < T , Keys extends keyof T > =
Partial < T > & Required < Pick < T , Keys >>
import type { RequireOnly } from '@proton/shared/lib/interfaces' ;
interface Config {
apiUrl : string ;
timeout : number ;
retries : number ;
debug : boolean ;
}
// Only apiUrl is required, rest are optional
type MinimalConfig = RequireOnly < Config , 'apiUrl' >;
const config : MinimalConfig = {
apiUrl: 'https://api.example.com'
// timeout, retries, debug are optional
};
RequireSome
Make specific properties required while keeping others as-is.
type RequireSome < T , Keys extends keyof T > =
T & Required < Pick < T , Keys >>
import type { RequireSome } from '@proton/shared/lib/interfaces' ;
interface User {
id ?: string ;
name ?: string ;
email ?: string ;
}
// Require id and email
type ValidUser = RequireSome < User , 'id' | 'email' >;
const user : ValidUser = {
id: '1' , // Required
email: '[email protected] ' , // Required
name: 'John' // Optional (was optional before)
};
DeepPartial
Recursively make all properties optional, including nested objects and arrays.
type DeepPartial < T > = T extends ( infer E )[]
? DeepPartial < E >[]
: T extends object
? { [ K in keyof T ] ?: DeepPartial < T [ K ]> }
: T | undefined
Nested Objects
Use Case: Patch Updates
import type { DeepPartial } from '@proton/shared/lib/interfaces' ;
interface Config {
api : {
url : string ;
timeout : number ;
retry : {
attempts : number ;
delay : number ;
};
};
features : string [];
}
type PartialConfig = DeepPartial < Config >;
const config : PartialConfig = {
api: {
retry: {
attempts: 3
// delay is optional
}
// url and timeout are optional
}
// features is optional
};
StrictRequired
Make all properties required and non-nullable.
type StrictRequired < T > = {
[ P in keyof T ] -?: NonNullable < T [ P ]>
}
import type { StrictRequired } from '@proton/shared/lib/interfaces' ;
interface User {
id ?: string | null ;
name ?: string | null ;
email ?: string | null ;
}
type ValidatedUser = StrictRequired < User >;
const user : ValidatedUser = {
id: '1' , // Required, can't be null
name: 'John' , // Required, can't be null
email: '[email protected] ' // Required, can't be null
};
Unwrap
Extract the resolved type from Promises or function return types.
type Unwrap < T > =
T extends Promise < infer U > ? U
: T extends ( ... args : any ) => Promise < infer U > ? U
: T extends ( ... args : any ) => infer U ? U
: T
Unwrap Promise
Unwrap Function Return
Practical Example
import type { Unwrap } from '@proton/shared/lib/interfaces' ;
type UserPromise = Promise <{ id : string ; name : string }>;
type User = Unwrap < UserPromise >;
// User = { id: string; name: string }
EitherOr
Create mutually exclusive properties.
type EitherOr < T , K extends keyof T > = {
[ P in K ] : {
[ Q in P ] : Required < Pick < T , P >>[ P ];
} & {
[ Q in Exclude < K , P >] ?: never ;
} & Omit < T , K >;
}[ K ]
Base type with all properties
Keys that should be mutually exclusive
Mutually Exclusive Props
ID vs Slug
import type { EitherOr } from '@proton/shared/lib/interfaces' ;
interface Payment {
amount : number ;
planIDs : string [];
planName : string ;
currency : string ;
}
// Can have either planIDs or planName, but not both
type PaymentInput = EitherOr < Payment , 'planIDs' | 'planName' >;
// Valid
const payment1 : PaymentInput = {
amount: 100 ,
planIDs: [ 'plan1' , 'plan2' ],
currency: 'USD'
};
// Valid
const payment2 : PaymentInput = {
amount: 100 ,
planName: 'premium' ,
currency: 'USD'
};
// Invalid - can't have both
// const payment3: PaymentInput = {
// amount: 100,
// planIDs: ['plan1'],
// planName: 'premium',
// currency: 'USD'
// };
API Types
Api
Generic API function type.
type Api = < T = any >( arg : object ) => Promise < T >
API request configuration
import type { Api } from '@proton/shared/lib/interfaces' ;
const api : Api = ( config ) => {
return fetch ( config . url , config ). then ( r => r . json ());
};
// Type-safe usage
interface User { id : string ; name : string ; }
const user = await api < User >({ url: '/users/1' });
// user is typed as User
ApiResponse
Base response structure from Proton API.
interface ApiResponse {
Code : number ;
}
HTTP status code (e.g., 1000 for success)
import type { ApiResponse } from '@proton/shared/lib/interfaces' ;
interface UserResponse extends ApiResponse {
User : {
ID : string ;
Name : string ;
Email : string ;
};
}
const response : UserResponse = await api ( '/users/me' );
if ( response . Code === 1000 ) {
console . log ( 'Success' , response . User );
}
Best Practices
Use Utility Types for Flexibility
// Instead of creating multiple interfaces
interface UserCreate {
name : string ;
email : string ;
}
interface UserUpdate {
id : string ;
name ?: string ;
email ?: string ;
}
// Use utility types
interface User {
id : string ;
name : string ;
email : string ;
}
type UserCreate = Omit < User , 'id' >;
type UserUpdate = RequireOnly < User , 'id' >;
Combine Utility Types
// Combine multiple utility types for complex scenarios
type PartialUser = Optional < User , 'avatar' | 'bio' >;
type RequiredEmail = RequireSome < PartialUser , 'email' >;
// Or chain them
type UserInput = RequireSome <
Optional < User , 'id' | 'createdAt' >,
'email'
>;
Type-Safe API Calls
import type { Api } from '@proton/shared/lib/interfaces' ;
interface GetUserResponse {
Code : number ;
User : User ;
}
const fetchUser = async ( api : Api , userId : string ) => {
const response = await api < GetUserResponse >({
url: `/users/ ${ userId } ` ,
method: 'get'
});
return response . User ; // Fully typed
};