UNPKG

balena-sdk

Version:
734 lines (656 loc) • 19.7 kB
import type { WebResourceFile } from 'balena-request'; import type { AnyObject, PropsOfType, StringKeyof, Dictionary, ExactlyExtends, } from './utils'; export interface WithId { id: number; } export interface PineDeferred { __id: number; } /** * When not selected-out holds a deferred. * When expanded hold an array with a single element. */ export type ConceptTypeNavigationResource<T extends object> = NavigationResource<T>; export type NavigationResource<T extends object> = [T] | PineDeferred; export type OptionalNavigationResource<T extends object> = | [] | [T] | PineDeferred | null; /** * When expanded holds an array, otherwise the property is not present. * Selecting is not suggested, * in that case it holds a deferred to the original resource. */ export type ReverseNavigationResource<T extends object> = T[] | undefined; export type AssociatedResource<T extends object> = | NavigationResource<T> | OptionalNavigationResource<T> | ReverseNavigationResource<T>; export type InferAssociatedResourceType<T> = T extends AssociatedResource<object> & any[] ? T[number] : never; export type SelectableProps<T> = // This allows us to get proper results when T is any/AnyObject, otherwise this returned never PropsOfType<T, ReverseNavigationResource<object>> extends StringKeyof<T> ? StringKeyof<T> : Exclude< StringKeyof<T>, PropsOfType<T, ReverseNavigationResource<object>> >; // This is the normal typed case export type ExpandableProps<T> = PropsOfType<T, AssociatedResource<object>> & string; type SelectedProperty<T, K extends keyof T> = T[K] extends NavigationResource<any> ? PineDeferred : T[K] extends OptionalNavigationResource<any> ? PineDeferred | null : T[K]; type SelectResultObject<T, Props extends keyof T> = { [P in Props]: SelectedProperty<T, P>; }; export type TypedSelectResult< T, TParams extends ODataOptions<T>, > = TParams['$select'] extends keyof T ? SelectResultObject<T, TParams['$select']> : TParams['$select'] extends Array<keyof T> ? SelectResultObject<T, TParams['$select'][number]> : TParams['$select'] extends '*' ? SelectResultObject<T, SelectableProps<T>> : undefined extends TParams['$select'] ? SelectResultObject<T, SelectableProps<T>> : never; type ExpandedProperty< T, K extends keyof T, KOpts extends ODataOptions<InferAssociatedResourceType<T[K]>>, > = KOpts extends ODataOptionsWithCount<any> ? number : T[K] extends NavigationResource<any> ? [TypedResult<InferAssociatedResourceType<T[K]>, KOpts>] : T[K] extends OptionalNavigationResource<any> ? [TypedResult<InferAssociatedResourceType<T[K]>, KOpts>] | [] : T[K] extends ReverseNavigationResource<any> ? Array<TypedResult<InferAssociatedResourceType<T[K]>, KOpts>> : never; export type ExpandResultObject<T, Props extends keyof T> = { [P in Props]-?: ExpandedProperty<T, P, object>; }; type ExpandResourceExpandObject< T, TResourceExpand extends ResourceExpand<T>, > = { [P in keyof TResourceExpand]-?: ExpandedProperty< T, P extends keyof T ? P : never, Exclude<TResourceExpand[P], undefined> >; }; export type TypedExpandResult<T, TParams extends ODataOptions<T>> = TParams['$expand'] extends ExpandableProps<T> ? ExpandResultObject<T, TParams['$expand']> : TParams['$expand'] extends ResourceExpand<T> ? keyof TParams['$expand'] extends ExpandableProps<T> ? ExpandResourceExpandObject<T, TParams['$expand']> : never : object; export type TypedResult<T, TParams extends ODataOptions<T> | undefined> = TParams extends ODataOptionsWithCount<T> ? number : TParams extends ODataOptions<T> ? Omit< TypedSelectResult<T, TParams>, keyof TypedExpandResult<T, TParams> > & TypedExpandResult<T, TParams> : undefined extends TParams ? TypedSelectResult<T, { $select: '*' }> : never; export type PostResult<T> = SelectResultObject< T, Exclude<StringKeyof<T>, PropsOfType<T, ReverseNavigationResource<object>>> >; export type WebResource = { filename: string; href: string; content_type?: string; content_disposition?: string; size?: number; }; // based on https://github.com/balena-io/pinejs-client-js/blob/master/core.d.ts type RawFilter = | string | Array<string | Filter<any>> | { $string: string; [index: string]: Filter<any> | string }; interface LambdaExpression<T> { [alias: string]: Filter<T>; } interface Lambda<T> { $alias: string; $expr: | LambdaExpression<T> // It seems that TS atm mixes things up after adding the following UNION rules // and allows having secondary filter props inside the $expr:LambdaExpression<T> // that are not props of the T // See: https://github.com/balena-io/balena-sdk/issues/714 | { 1: number } | { $and?: Array<LambdaExpression<T>> } | { $or?: Array<LambdaExpression<T>> } | { $not?: LambdaExpression<T> }; } type OrderByDirection = 'asc' | 'desc'; type OrderBy<T> = | `${string} ${OrderByDirection}` // TODO next major: Change to: `${keyof T & string} ${OrderByDirection}` | [keyof T & string, OrderByDirection] | Array<OrderBy<T>> | { [k in keyof T]?: OrderByDirection } | ({ [k in ExpandableProps<T>]?: { $count: ODataCountOptions<InferAssociatedResourceType<T[k]>>; }; } & { $dir: OrderByDirection; }); type AssociatedResourceFilter<T> = T extends NonNullable<ReverseNavigationResource<object>> ? FilterObj<InferAssociatedResourceType<T>> : FilterObj<InferAssociatedResourceType<T>> | number | null; type ResourceObjFilterPropValue<T, k extends keyof T> = T[k] extends AssociatedResource<object> ? AssociatedResourceFilter<T[k]> : T[k] | FilterExpressions<T[k]> | null; type ResourceObjFilter<T> = { [k in keyof T]?: ResourceObjFilterPropValue<T, k>; }; type Filter<T> = FilterObj<T>; type FilterObj<T> = ResourceObjFilter<T> | FilterExpressions<T>; type FilterBaseType = string | number | null | boolean | Date; type NestedFilter<T> = FilterObj<T> | FilterArray<T> | FilterBaseType; type FilterArray<T> = Array<NestedFilter<T>>; type FilterOperationValue<T> = | NestedFilter<T> | FilterFunctionExpressions<NestedFilter<T>>; type FilterFunctionValue<T> = NestedFilter<T>; type FilterFunctionExpressions<T> = Pick< FilterExpressions<T>, FilterFunctionKey >; type FilterOperationKey = | '$ne' | '$eq' | '$gt' | '$ge' | '$lt' | '$le' | '$add' | '$sub' | '$mul' | '$div' | '$mod'; type FilterFunctionKey = | '$contains' | '$endswith' | '$startswith' | '$length' | '$indexof' | '$substring' | '$tolower' | '$toupper' | '$trim' | '$concat' | '$year' | '$month' | '$day' | '$hour' | '$minute' | '$second' | '$fractionalseconds' | '$date' | '$time' | '$totaloffsetminutes' | '$now' | '$maxdatetime' | '$mindatetime' | '$totalseconds' | '$round' | '$floor' | '$ceiling' | '$isof' | '$cast'; interface FilterExpressions<T> { '@'?: string; $raw?: RawFilter; $?: string | string[]; $count?: Filter<T>; $and?: NestedFilter<T>; $or?: NestedFilter<T>; $in?: NestedFilter<T>; $not?: NestedFilter<T>; $any?: Lambda<T>; $all?: Lambda<T>; // Filter operations $ne?: FilterOperationValue<T>; $eq?: FilterOperationValue<T>; $gt?: FilterOperationValue<T>; $ge?: FilterOperationValue<T>; $lt?: FilterOperationValue<T>; $le?: FilterOperationValue<T>; $add?: FilterOperationValue<T>; $sub?: FilterOperationValue<T>; $mul?: FilterOperationValue<T>; $div?: FilterOperationValue<T>; $mod?: FilterOperationValue<T>; // Filter functions $contains?: FilterFunctionValue<T>; $endswith?: FilterFunctionValue<T>; $startswith?: FilterFunctionValue<T>; $length?: FilterFunctionValue<T>; $indexof?: FilterFunctionValue<T>; $substring?: FilterFunctionValue<T>; $tolower?: FilterFunctionValue<T>; $toupper?: FilterFunctionValue<T>; $trim?: FilterFunctionValue<T>; $concat?: FilterFunctionValue<T>; $year?: FilterFunctionValue<T>; $month?: FilterFunctionValue<T>; $day?: FilterFunctionValue<T>; $hour?: FilterFunctionValue<T>; $minute?: FilterFunctionValue<T>; $second?: FilterFunctionValue<T>; $fractionalseconds?: FilterFunctionValue<T>; $date?: FilterFunctionValue<T>; $time?: FilterFunctionValue<T>; $totaloffsetminutes?: FilterFunctionValue<T>; $now?: FilterFunctionValue<T>; $maxdatetime?: FilterFunctionValue<T>; $mindatetime?: FilterFunctionValue<T>; $totalseconds?: FilterFunctionValue<T>; $round?: FilterFunctionValue<T>; $floor?: FilterFunctionValue<T>; $ceiling?: FilterFunctionValue<T>; $isof?: FilterFunctionValue<T>; $cast?: FilterFunctionValue<T>; } export type ResourceExpand<T> = { [k in ExpandableProps<T>]?: ODataOptions<InferAssociatedResourceType<T[k]>>; }; type ResourceExpandWithSelect<T> = { [k in ExpandableProps<T>]?: ODataOptionsStrict< InferAssociatedResourceType<T[k]> >; }; type BaseExpand<T> = ResourceExpand<T> | ExpandableProps<T>; export type Expand<T> = BaseExpand<T> | Array<BaseExpand<T>>; type ExpandWithSelect<T> = | ResourceExpandWithSelect<T> | Array<ResourceExpandWithSelect<T>>; export type ODataMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; export interface ODataOptionsWithoutCount<T> { $select?: Array<SelectableProps<T>> | SelectableProps<T> | '*'; $filter?: Filter<T>; $expand?: Expand<T>; $orderby?: OrderBy<T>; $top?: number; $skip?: number; } export type ODataCountOptions<T> = Pick<ODataOptionsWithoutCount<T>, '$filter'>; export interface ODataOptions<T> extends ODataOptionsWithoutCount<T> { $count?: ODataCountOptions<T>; } export interface ODataOptionsWithCount<T> extends ODataOptionsWithoutCount<T> { $count: NonNullable<ODataCountOptions<T>>; } export type ODataOptionsStrict<T> = Omit< ODataOptions<T>, '$select' | '$expand' | '$count' > & ( | { $select: ODataOptions<T>['$select']; $expand?: ExpandWithSelect<T>; } | { $count: ODataOptionsWithoutCount<T>; } ); export type ODataOptionsWithFilter<T> = ODataOptions<T> & Required<Pick<ODataOptions<T>, '$filter'>>; export type ReplaceWebResource<K> = K extends WebResource ? WebResourceFile : K; export type SubmitBody<T> = { [k in keyof T]?: T[k] extends AssociatedResource<object> ? number | null : ReplaceWebResource<T[k]>; }; type BaseResourceId = | string | number | Date | { '@': string; }; type ResourceAlternateKey<T> = SubmitBody<T>; type ResourceId<T> = BaseResourceId | ResourceAlternateKey<T>; export interface ParamsObj<T> { apiPrefix?: string; method?: ODataMethod; resource?: string; id?: ResourceId<T>; url?: string; body?: SubmitBody<T>; passthrough?: AnyObject; passthroughByMethod?: { [method in ODataMethod]: AnyObject }; options?: ODataOptions<T>; customOptions?: AnyObject; retry?: RetryParameters; } export interface ParamsObjWithId<T> extends ParamsObj<T> { id: ResourceId<T>; } export interface ParamsObjWithCount<T> extends ParamsObj<T> { options: { $count: NonNullable<ODataOptions<T>['$count']> }; } export type ParamsObjStrict<T> = Omit<ParamsObj<T>, 'options'> & { options: ODataOptionsStrict<T>; }; export interface ParamsObjWithFilter<T> extends ParamsObj<T> { options: ODataOptionsWithFilter<T>; } export interface GetOrCreateParams<T> extends Omit<ParamsObj<T>, 'method'> { id: ResourceAlternateKey<T>; resource: string; body: SubmitBody<T>; } export interface UpsertParams<T> extends Omit<ParamsObj<T>, 'id' | 'method' | 'options'> { id: ResourceAlternateKey<T>; resource: string; body: SubmitBody<T>; } export declare type Primitive = null | string | number | boolean | Date; export declare type ParameterAlias = Primitive; export declare type PreparedFn<T extends Dictionary<ParameterAlias>, U, R> = ( parameterAliases?: T, body?: ParamsObj<R>['body'], passthrough?: ParamsObj<R>['passthrough'], ) => U; interface PollOnObj { unsubscribe: () => void; } declare class Poll<T> { private intervalTime; private subscribers; private stopped; private pollInterval?; private requestFn; constructor(requestFn: () => Promise<T>, intervalTime?: number); setPollInterval(intervalTime: number): void; runRequest(): Promise<void>; on(name: 'data', fn: (response: Promise<T>) => void): PollOnObj; on(name: 'error', fn: (err: any) => void): PollOnObj; start(): void; stop(): void; destroy(): void; private restartTimeout; } declare const validParams: readonly [ 'apiPrefix', 'passthrough', 'passthroughByMethod', 'retry', ]; export type RetryParametersObj = { canRetry?: (err: any) => boolean; onRetry?: ( prevErr: any, delayMs: number, attempt: number, maxAttempts: number, ) => void; getRetryAfterHeader?: (err: unknown) => string | undefined; minDelayMs?: number; maxDelayMs?: number; maxAttempts?: number; }; export type RetryParameters = RetryParametersObj | false; export interface SubscribeParams<T> extends ParamsObj<T> { method?: 'GET'; pollInterval?: number; } export interface SubscribeParamsWithCount<T> extends ParamsObjWithCount<T> { method?: 'GET'; pollInterval?: number; } export interface SubscribeParamsWithId<T> extends ParamsObjWithId<T> { method?: 'GET'; pollInterval?: number; } export type ConstructorParams = Pick< ParamsObj<unknown>, (typeof validParams)[number] >; export interface Pine<ResourceTypeMap extends object = object> { apiPrefix: string; passthrough: AnyObject; passthroughByMethod: AnyObject; backendParams?: AnyObject; retry: RetryParameters; clone(params: string | ConstructorParams, backendParams?: AnyObject): this; // Fully typed result overloads get< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObjWithCount< ResourceTypeMap[P['resource']] >, >( params: ExactlyExtends< P, ParamsObjWithCount<ResourceTypeMap[P['resource']]> >, ): Promise<number>; get< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObjWithId<ResourceTypeMap[P['resource']]>, >( params: ExactlyExtends<P, ParamsObjWithId<ResourceTypeMap[P['resource']]>>, ): Promise< TypedResult<ResourceTypeMap[P['resource']], P['options']> | undefined >; get< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObj<ResourceTypeMap[P['resource']]>, >( params: ExactlyExtends<P, ParamsObj<ResourceTypeMap[P['resource']]>>, ): Promise<Array<TypedResult<ResourceTypeMap[P['resource']], P['options']>>>; // User provided resource type overloads get<T extends object>(params: ParamsObjWithCount<T>): Promise<number>; get<T extends object>(params: ParamsObjWithId<T>): Promise<T | undefined>; get<T extends object>(params: ParamsObj<T>): Promise<T[]>; get<T extends object, Result>(params: ParamsObj<T>): Promise<Result>; patch<T>(params: ParamsObjWithId<T> | ParamsObjWithFilter<T>): Promise<'OK'>; post< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObj<ResourceTypeMap[P['resource']]>, >( params: ExactlyExtends<P, ParamsObj<ResourceTypeMap[P['resource']]>> & { body: object; }, ): Promise<PostResult<ResourceTypeMap[P['resource']]>>; post<T>( params: ParamsObj<T> & { body: object }, ): Promise<PostResult<T & { id: number }>>; delete<T>(params: ParamsObjWithId<T> | ParamsObjWithFilter<T>): Promise<'OK'>; getOrCreate<T>(params: GetOrCreateParams<T>): Promise<T>; upsert<T>(params: UpsertParams<T>): Promise<T | 'OK'>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObjWithCount<R> & { method?: 'GET'; }, ): PreparedFn<T, Promise<number>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObjWithId<R> & { method?: 'GET'; }, ): PreparedFn<T, Promise<R | undefined>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObj<R> & { method?: 'GET'; }, ): PreparedFn<T, Promise<R[]>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObj<R> & { method: 'POST'; }, ): PreparedFn<T, Promise<R & { id: number }>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObj<R> & { method: 'PATCH' | 'DELETE'; }, ): PreparedFn<T, Promise<'OK'>, R>; compile<R extends keyof ResourceTypeMap>( params: { resource: NonNullable<ParamsObj<R>>; } & ParamsObj<R>, ): string; subscribe<T>( params: SubscribeParamsWithCount<T> & { method?: 'GET'; }, ): Poll<number>; subscribe<T>( params: SubscribeParamsWithId<T> & { method?: 'GET'; }, ): Poll<T | undefined>; subscribe<T>( params: SubscribeParams<T> & { method?: 'GET'; }, ): Poll<T[]>; subscribe<T>( params: SubscribeParams<T> & { method: 'POST'; }, ): Poll<T & { id: number }>; subscribe<T>( params: SubscribeParams<T> & { method: 'PATCH' | 'DELETE'; }, ): Poll<'OK'>; } /** * A variant that makes $select mandatory, helping to create * requests that explicitly fetch only what your code needs. */ export type PineStrict<ResourceTypeMap extends object = object> = Omit< Pine, 'get' | 'prepare' | 'subscribe' > & { // Fully typed result overloads get< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObjWithCount< ResourceTypeMap[P['resource']] >, >( params: ExactlyExtends< P, ParamsObjWithCount<ResourceTypeMap[P['resource']]> >, ): Promise<number>; get< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObjWithId< ResourceTypeMap[P['resource']] > & ParamsObjStrict<ResourceTypeMap[P['resource']]>, >( params: ExactlyExtends< P, ParamsObjWithId<ResourceTypeMap[P['resource']]> & ParamsObjStrict<ResourceTypeMap[P['resource']]> >, ): Promise< TypedResult<ResourceTypeMap[P['resource']], P['options']> | undefined >; get< R extends keyof ResourceTypeMap, P extends { resource: R } & ParamsObjStrict<ResourceTypeMap[P['resource']]>, >( params: ExactlyExtends<P, ParamsObjStrict<ResourceTypeMap[P['resource']]>>, ): Promise<Array<TypedResult<ResourceTypeMap[P['resource']], P['options']>>>; // User provided resource type overloads get<T extends object>( params: ParamsObjWithCount<NoInfer<T>>, ): Promise<number>; get<T extends object>( params: ParamsObjWithId<NoInfer<T>> & ParamsObjStrict<NoInfer<T>>, ): Promise<T | undefined>; get<T extends object>(params: ParamsObjStrict<NoInfer<T>>): Promise<T[]>; get<T extends object, Result extends number>( params: ParamsObj<NoInfer<T>>, ): Promise<Result>; get<T extends object, Result>( params: ParamsObjStrict<NoInfer<T>>, ): Promise<Result>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObjWithCount<R> & { method?: 'GET'; }, ): PreparedFn<T, Promise<number>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObjWithId<R> & ParamsObjStrict<R> & { method?: 'GET'; }, ): PreparedFn<T, Promise<R | undefined>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObj<R> & ParamsObjStrict<R> & { method?: 'GET'; }, ): PreparedFn<T, Promise<R[]>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObj<R> & { method: 'POST'; }, ): PreparedFn<T, Promise<R & { id: number }>, R>; prepare<T extends Dictionary<ParameterAlias>, R>( params: ParamsObj<R> & { method: 'PATCH' | 'DELETE'; }, ): PreparedFn<T, Promise<'OK'>, R>; subscribe<T>( params: SubscribeParamsWithCount<T> & { method?: 'GET'; }, ): Poll<number>; subscribe<T>( params: SubscribeParamsWithId<T> & ParamsObjStrict<T> & { method?: 'GET'; }, ): Poll<T | undefined>; subscribe<T>( params: SubscribeParams<T> & ParamsObjStrict<T> & { method?: 'GET'; }, ): Poll<T[]>; subscribe<T>( params: SubscribeParams<T> & { method: 'POST'; }, ): Poll<T & { id: number }>; subscribe<T>( params: SubscribeParams<T> & { method: 'PATCH' | 'DELETE'; }, ): Poll<'OK'>; };