reso.js
Version:
A robust, Typescript-first Node.js client designed for interacting with RESO Web API services, fully aligned with the current RESO Web API specification.
167 lines (156 loc) • 5.91 kB
TypeScript
import { FetchOptions, ResponseType, FetchHook, FetchContext, FetchResponse } from 'ofetch';
import PQueue from 'p-queue';
type AuthOptionType = 'bearer' | 'credentials';
interface BaseAuthOptions {
type: AuthOptionType;
}
interface AuthBearerOptions extends BaseAuthOptions {
type: 'bearer';
credentials: {
token: string;
tokenType?: string;
};
}
interface AuthClientCredentialsOptions extends BaseAuthOptions {
type: 'credentials';
credentials: {
tokenURL: string;
clientId: string;
clientSecret: string;
scope?: string;
grantType?: string;
};
refreshBuffer?: number;
}
type AuthOptions = AuthBearerOptions | AuthClientCredentialsOptions;
type CreateAuthOptions = AuthOptions;
interface TransportError {
code: string | number;
message: string;
target: string;
details: TransportErrorDetail[];
[key: string]: unknown;
}
interface TransportErrorDetail {
code: string;
target: string;
message: string;
}
declare enum TransportErrorCode {
ServiceUnavailable = "SERVICE_UNAVAILABLE",
Forbidden = "FORBIDDEN",
BadRequest = "BAD_REQUEST",
NotFound = "NOT_FOUND",
RequestEntityTooLarge = "REQUEST_ENTITY_TO_LARGE",
UnsupportedMedia = "UNSUPPORTED_MEDIA",
TooManyRequests = "TOO_MANY_REQUESTS",
InternalServerError = "INTERNAL_SERVER_ERROR",
NotImplemented = "NOT_IMPLEMENTED",
Unknown = "UNKNOWN"
}
type FeedErrorOptions = {
message?: string;
} & Partial<Pick<TransportError, 'code' | 'target' | 'details'>>;
interface CreateLimiterOptions {
duration?: number;
points?: number;
}
type FeedLimiter = PQueue;
type FeedHttpOptions = Omit<FetchOptions, 'onResponse' | 'onResponseError' | 'onRequest' | 'onRequestError' | 'baseURL'> & Required<Pick<FetchOptions, 'baseURL'>>;
interface FeedHooksOptions<T = unknown, R extends ResponseType = ResponseType> {
onRequest?: FetchHook<FetchContext<T, R>>[];
onRequestError?: FetchHook<FetchContext<T, R> & {
error: Error;
}>[];
onResponse?: FetchHook<FetchContext<T, R> & {
response: FetchResponse<T>;
}>[];
onResponseError?: FetchHook<FetchContext<T, R> & {
response: FetchResponse<T>;
}>[];
}
interface FeedOptions {
http: FeedHttpOptions;
hooks?: FeedHooksOptions;
}
interface CreateFeedOptions {
http: FeedHttpOptions;
hooks?: FeedHooksOptions;
limiter?: CreateLimiterOptions;
auth?: CreateAuthOptions;
}
interface RequestOptions {
query?: string | undefined;
}
type ResourceId = string | number;
type ResourceQuery = string;
interface FeedBaseResponse<R> {
context?: string;
data: R | R[];
}
interface FeedEntityResponse<R> extends FeedBaseResponse<R> {
data: R;
}
interface FeedCollectionResponse<R> extends FeedBaseResponse<R> {
nextLink?: string;
count?: string | number;
data: R[];
}
/**
* Conditional type utility that determines if a given type 'T' resolves to 'any'.
*
* If T is any it resolves to type 'Y'
* If T is not any it resolves to type 'N'
*/
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
/**
* Resolves the type of the Resource (R) based on the Schema and the requested Resource.
*
* If Schema is 'any', it defaults to Record<string, unknown>.
* If Schema is specific, it looks up Schema[R].
*/
type ResolveResourceType<Schema, R> = IfAny<Schema, Record<string, unknown>, // Fallback for 'any' or 'unknown' Schema
Schema extends Record<string, any> ? (R extends keyof Schema ? Schema[R] : never) : never>;
declare class Feed<Schema> {
http: FeedOptions['http'];
hooks: FeedOptions['hooks'];
constructor(opts: FeedOptions);
/**
* Fetch the OData $metadata.
*
* @param [query] The optional query to apply for the metadata request (rarely used).
* @returns The raw metadata XML document
*/
$metadata(query?: ResourceQuery): Promise<string>;
/**
* Fetch a single resource entity by its unique ID.
* @param resource The name of the resource collection.
* @param id The unique identifier for the specific entity.
* @param [query] The optional query to apply.
* @returns The entity
*/
readById<R extends IfAny<Schema, string, keyof Schema>>(resource: R, id: ResourceId, query?: ResourceQuery): Promise<FeedEntityResponse<ResolveResourceType<Schema, R>>>;
/**
* Fetch a resource collection.
*
* The generator will yield a response for each page of results
* and automatically follow the `@odata.nextLink` until all entities are retrieved.
*
* @param resource The name of the resource collection.
* @param [query] The optional query to apply.
* @returns An async generator that yields paginated collection responses.
*/
readByQuery<R extends IfAny<Schema, string, keyof Schema>>(resource: R, query?: ResourceQuery): AsyncGenerator<FeedCollectionResponse<ResolveResourceType<Schema, R>>, void, unknown>;
request<R>(path: string, options?: RequestOptions): Promise<string | FeedCollectionResponse<R> | FeedEntityResponse<R>>;
}
declare function createFeed<Schema>(opts: CreateFeedOptions): Feed<Schema>;
declare class FeedError extends Error {
name: TransportErrorCode;
code: string | number;
target: string | null;
details: NonNullable<FeedErrorOptions['details']>;
constructor(opts: FeedErrorOptions);
toString(): string;
}
export { FeedError, TransportErrorCode, createFeed };
export type { AuthBearerOptions, AuthClientCredentialsOptions, AuthOptionType, AuthOptions, BaseAuthOptions, CreateAuthOptions, CreateFeedOptions, CreateLimiterOptions, FeedBaseResponse, FeedCollectionResponse, FeedEntityResponse, FeedErrorOptions, FeedHooksOptions, FeedHttpOptions, FeedLimiter, FeedOptions, IfAny, RequestOptions, ResolveResourceType, ResourceId, ResourceQuery, TransportError, TransportErrorDetail };