import {
Adapter,
Condition,
Repository,
Sequence,
SequenceOptions,
UnsupportedError,
} from "@decaf-ts/core";
import { BaseError, Context, OperationKeys } from "@decaf-ts/db-decorators";
import { HttpConfig, HttpFlags } from "./types";
import { Constructor, Model } from "@decaf-ts/decorator-validation";
import { RestService } from "./RestService";
import { Statement } from "@decaf-ts/core";
/**
* @description Abstract HTTP adapter for REST API interactions
* @summary Provides a base implementation for HTTP adapters with methods for CRUD operations,
* URL construction, and error handling. This class extends the core Adapter class and
* implements the necessary methods for HTTP communication. Concrete implementations
* must provide specific HTTP client functionality.
* @template Y - The native HTTP client type
* @template Q - The query type used by the adapter
* @template F - The HTTP flags type, extending HttpFlags
* @template C - The context type, extending Context<F>
* @param {Y} native - The native HTTP client instance
* @param {HttpConfig} config - Configuration for the HTTP adapter
* @param {string} flavour - The adapter flavor identifier
* @param {string} [alias] - Optional alias for the adapter
* @example
* ```typescript
* // Example implementation with Axios
* class AxiosAdapter extends HttpAdapter<AxiosInstance, AxiosRequestConfig> {
* constructor(config: HttpConfig) {
* super(axios.create(), config, 'axios');
* }
*
* async request<V>(details: AxiosRequestConfig): Promise<V> {
* const response = await this.native.request(details);
* return response.data;
* }
*
* // Implement other abstract methods...
* }
* ```
* @class
*/
export abstract class HttpAdapter<
Y,
Q,
F extends HttpFlags = HttpFlags,
C extends Context<F> = Context<F>,
> extends Adapter<Y, Q, F, C> {
protected constructor(
native: Y,
protected config: HttpConfig,
flavour: string,
alias?: string
) {
super(native, flavour, alias);
}
/**
* @description Generates operation flags with HTTP headers
* @summary Extends the base flags method to include HTTP-specific headers for operations.
* This method adds an empty headers object to the flags returned by the parent class.
* @template F - The Repository Flags type
* @template M - The model type
* @param {OperationKeys.CREATE|OperationKeys.READ|OperationKeys.UPDATE|OperationKeys.DELETE} operation - The operation type
* @param {Constructor<M>} model - The model constructor
* @param {Partial<F>} overrides - Optional flag overrides
* @return {F} The flags object with headers
*/
override flags<M extends Model>(
operation:
| OperationKeys.CREATE
| OperationKeys.READ
| OperationKeys.UPDATE
| OperationKeys.DELETE,
model: Constructor<M>,
overrides: Partial<F>
) {
return Object.assign(super.flags<M>(operation, model, overrides), {
headers: {},
});
}
/**
* @description Returns the repository constructor for this adapter
* @summary Provides the RestService class as the repository implementation for this HTTP adapter.
* This method is used to create repository instances that work with this adapter type.
* @template M - The model type
* @return {Constructor<Repository<M, Q, HttpAdapter<Y, Q, F, C>>>} The repository constructor
*/
override repository<M extends Model>(): Constructor<
Repository<M, Q, HttpAdapter<Y, Q, F, C>>
> {
return RestService as unknown as Constructor<
Repository<M, Q, HttpAdapter<Y, Q, F, C>>
>;
}
/**
* @description Constructs a URL for API requests
* @summary Builds a complete URL for API requests using the configured protocol and host,
* the specified table name, and optional query parameters. The method handles URL encoding.
* @param {string} tableName - The name of the table or endpoint
* @param {Record<string, string | number>} [queryParams] - Optional query parameters
* @return {string} The encoded URL string
*/
protected url(
tableName: string,
queryParams?: Record<string, string | number>
) {
const url = new URL(
`${this.config.protocol}://${this.config.host}/${tableName}`
);
if (queryParams)
Object.entries(queryParams).forEach(([key, value]) =>
url.searchParams.append(key, value.toString())
);
return encodeURI(url.toString());
}
/**
* @description Parses and converts errors to BaseError type
* @summary Processes errors that occur during HTTP operations and converts them to
* the appropriate BaseError type. Currently returns the error as-is, but can be
* extended to handle specific error messages differently.
* @param {Error} err - The error to parse
* @return {BaseError} The parsed error as a BaseError
*/
parseError(err: Error): BaseError {
const { message } = err;
switch (message) {
default:
return err as BaseError;
}
}
/**
* @description Initializes the HTTP adapter
* @summary Placeholder method for adapter initialization. This method is currently
* a no-op but can be overridden by subclasses to perform initialization tasks.
* @param {...any[]} args - Initialization arguments
* @return {Promise<void>} A promise that resolves when initialization is complete
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async initialize(...args: any[]): Promise<void> {
// do nothing
}
/**
* @description Sends an HTTP request
* @summary Abstract method that must be implemented by subclasses to send HTTP requests
* using the native HTTP client. This is the core method for making API calls.
* @template V - The response value type
* @param {Q} details - The request details specific to the HTTP client
* @return {Promise<V>} A promise that resolves with the response data
*/
abstract request<V>(details: Q): Promise<V>;
/**
* @description Creates a new resource
* @summary Abstract method that must be implemented by subclasses to create a new resource
* via HTTP. This typically corresponds to a POST request.
* @param {string} tableName - The name of the table or endpoint
* @param {string|number} id - The identifier for the resource
* @param {Record<string, any>} model - The data model to create
* @param {...any[]} args - Additional arguments
* @return {Promise<Record<string, any>>} A promise that resolves with the created resource
*/
abstract override create(
tableName: string,
id: string | number,
model: Record<string, any>,
...args: any[]
): Promise<Record<string, any>>;
/**
* @description Retrieves a resource by ID
* @summary Abstract method that must be implemented by subclasses to retrieve a resource
* via HTTP. This typically corresponds to a GET request.
* @param {string} tableName - The name of the table or endpoint
* @param {string|number|bigint} id - The identifier for the resource
* @param {...any[]} args - Additional arguments
* @return {Promise<Record<string, any>>} A promise that resolves with the retrieved resource
*/
abstract override read(
tableName: string,
id: string | number | bigint,
...args: any[]
): Promise<Record<string, any>>;
/**
* @description Updates an existing resource
* @summary Abstract method that must be implemented by subclasses to update a resource
* via HTTP. This typically corresponds to a PUT or PATCH request.
* @param {string} tableName - The name of the table or endpoint
* @param {string|number} id - The identifier for the resource
* @param {Record<string, any>} model - The updated data model
* @param {...any[]} args - Additional arguments
* @return {Promise<Record<string, any>>} A promise that resolves with the updated resource
*/
abstract override update(
tableName: string,
id: string | number,
model: Record<string, any>,
...args: any[]
): Promise<Record<string, any>>;
/**
* @description Deletes a resource by ID
* @summary Abstract method that must be implemented by subclasses to delete a resource
* via HTTP. This typically corresponds to a DELETE request.
* @param {string} tableName - The name of the table or endpoint
* @param {string|number|bigint} id - The identifier for the resource to delete
* @param {...any[]} args - Additional arguments
* @return {Promise<Record<string, any>>} A promise that resolves with the deletion result
*/
abstract override delete(
tableName: string,
id: string | number | bigint,
...args: any[]
): Promise<Record<string, any>>;
/**
* @description Executes a raw query
* @summary Method for executing raw queries directly with the HTTP client.
* This method is not supported by default in HTTP adapters and throws an UnsupportedError.
* Subclasses can override this method to provide implementation.
* @template R - The result type
* @param {Q} rawInput - The raw query input
* @param {boolean} process - Whether to process the result
* @param {...any[]} args - Additional arguments
* @return {Promise<R>} A promise that resolves with the query result
* @throws {UnsupportedError} Always throws as this method is not supported by default
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
raw<R>(rawInput: Q, process: boolean, ...args: any[]): Promise<R> {
throw new UnsupportedError(
"Api is not natively available for HttpAdapters. If required, please extends this class"
);
}
/**
* @description Creates a sequence
* @summary Method for creating a sequence for generating unique identifiers.
* This method is not supported by default in HTTP adapters and throws an UnsupportedError.
* Subclasses can override this method to provide implementation.
* @param {SequenceOptions} options - Options for creating the sequence
* @return {Promise<Sequence>} A promise that resolves with the created sequence
* @throws {UnsupportedError} Always throws as this method is not supported by default
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Sequence(options: SequenceOptions): Promise<Sequence> {
throw new UnsupportedError(
"Api is not natively available for HttpAdapters. If required, please extends this class"
);
}
/**
* @description Creates a statement for querying
* @summary Method for creating a statement for building and executing queries.
* This method is not supported by default in HTTP adapters and throws an UnsupportedError.
* Subclasses can override this method to provide implementation.
* @template M - The model type
* @template ! - The raw query type
* @return {Statement<Q, M, any>} A statement object for building queries
* @throws {UnsupportedError} Always throws as this method is not supported by default
*/
override Statement<M extends Model>(): Statement<Q, M, any> {
throw new UnsupportedError(
"Api is not natively available for HttpAdapters. If required, please extends this class"
);
}
/**
* @description Parses a condition into a query
* @summary Method for parsing a condition object into a query format understood by the HTTP client.
* This method is not supported by default in HTTP adapters and throws an UnsupportedError.
* Subclasses can override this method to provide implementation.
* @param {Condition<any>} condition - The condition to parse
* @return {Q} The parsed query
* @throws {UnsupportedError} Always throws as this method is not supported by default
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
parseCondition(condition: Condition<any>): Q {
throw new UnsupportedError(
"Api is not natively available for HttpAdapters. If required, please extends this class"
);
}
}
Source