315 lines
11 KiB
TypeScript
315 lines
11 KiB
TypeScript
import type { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
import type {
|
|
BaseEntity,
|
|
ApiURLMap,
|
|
RequestMethodMap,
|
|
RequestDescriptor,
|
|
ExecuteOptions,
|
|
DataServicesConfig,
|
|
} from './types';
|
|
import type { ApiResponse } from '../http-client/types';
|
|
import { interpolateUrl } from './url-builder';
|
|
import { DEFAULT_METHODS, DESCRIPTORS, makeDefaultURLs } from './constants';
|
|
|
|
/**
|
|
* Abstract base class for remote data services.
|
|
*
|
|
* Provides a generic `execute()` method that eliminates the 22
|
|
* near-identical methods from the legacy `BaseRemoteDataServices`.
|
|
* Each operation is reduced to a one-liner calling `execute()`
|
|
* with the appropriate descriptor.
|
|
*
|
|
* **Key architectural differences from legacy:**
|
|
* - Receives an **injected** AxiosInstance (no global axios import)
|
|
* - Returns `Promise<ApiResponse<T>>` (no callback-based onSuccess/onFailed)
|
|
* - All operations are fully typed end-to-end
|
|
* - Includes `customRequest<T>()` as an escape hatch for non-standard endpoints
|
|
*
|
|
* @typeParam E - The domain entity type (must extend BaseEntity)
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* class BookingDataServices extends BaseRemoteDataServices<BookingEntity> {}
|
|
*
|
|
* const services = new BookingDataServices(apiClient, {
|
|
* apiUrl: '/bookings',
|
|
* moduleKey: 'BOOKING',
|
|
* });
|
|
*
|
|
* const { data, status } = await services.getOne<BookingEntity>('42');
|
|
* ```
|
|
*/
|
|
export abstract class BaseRemoteDataServices<E extends BaseEntity = BaseEntity> {
|
|
/** The injected, isolated HTTP client instance. */
|
|
protected readonly httpClient: AxiosInstance;
|
|
|
|
/** Resolved URL map for all operations. */
|
|
protected readonly urls: ApiURLMap;
|
|
|
|
/** Resolved HTTP method map for all operations. */
|
|
protected readonly methods: RequestMethodMap;
|
|
|
|
/** Module key for the 'ex-module-key' audit header. */
|
|
protected readonly moduleKey: string | undefined;
|
|
|
|
constructor(httpClient: AxiosInstance, config: DataServicesConfig) {
|
|
this.httpClient = httpClient;
|
|
this.moduleKey = config.moduleKey;
|
|
|
|
this.urls = {
|
|
...makeDefaultURLs(config.apiUrl ?? ''),
|
|
...(config.urls ?? {}),
|
|
};
|
|
|
|
this.methods = {
|
|
...DEFAULT_METHODS,
|
|
...(config.methods ?? {}),
|
|
};
|
|
}
|
|
|
|
// ─── Generic Executor ───────────────────────────────────────────
|
|
|
|
/**
|
|
* The single generic request executor.
|
|
*
|
|
* All standard operations delegate to this method with a
|
|
* pre-defined descriptor. This is the engine that replaces
|
|
* 22 near-identical legacy methods.
|
|
*
|
|
* @typeParam T - Expected response data type
|
|
* @param descriptor - Defines which URL, method, and action to use
|
|
* @param options - Dynamic URL params and additional Axios config
|
|
* @returns Typed API response with data and status
|
|
*/
|
|
protected async execute<T = unknown>(
|
|
descriptor: RequestDescriptor,
|
|
options?: ExecuteOptions,
|
|
): Promise<ApiResponse<T>> {
|
|
const { urlKey, methodKey, action } = descriptor;
|
|
|
|
const response = await this.httpClient.request<T>({
|
|
url: interpolateUrl(this.urls[urlKey], options?.variableURL),
|
|
method: this.methods[methodKey],
|
|
...(options?.config ?? {}),
|
|
headers: {
|
|
...(this.moduleKey ? { 'ex-module-key': this.moduleKey } : {}),
|
|
'ex-module-action': action,
|
|
...(options?.config?.headers ?? {}),
|
|
},
|
|
telemetryContext: options?.telemetryContext ?? options?.config?.telemetryContext // FIXME,
|
|
});
|
|
|
|
return { data: response.data, status: response.status };
|
|
}
|
|
|
|
// ─── Escape Hatch ───────────────────────────────────────────────
|
|
|
|
/**
|
|
* Execute a fully custom request that doesn't fit standard CRUD.
|
|
*
|
|
* Use this for non-standard endpoints like `/calculate-tax`,
|
|
* custom aggregations, or third-party integrations.
|
|
*
|
|
* The request still flows through the injected httpClient, so
|
|
* all interceptors (auth, observability, error handling) are
|
|
* preserved automatically.
|
|
*
|
|
* @typeParam T - Expected response data type
|
|
* @param config - Complete Axios request configuration
|
|
* @returns Typed API response with data and status
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const tax = await services.customRequest<TaxResult>({
|
|
* url: '/bookings/42/calculate-tax',
|
|
* method: 'POST',
|
|
* data: { items: [...] },
|
|
* });
|
|
* ```
|
|
*/
|
|
async customRequest<T = unknown>(
|
|
config: AxiosRequestConfig,
|
|
): Promise<ApiResponse<T>> {
|
|
const response = await this.httpClient.request<T>({
|
|
...config,
|
|
headers: {
|
|
...(this.moduleKey ? { 'ex-module-key': this.moduleKey } : {}),
|
|
...(config.headers ?? {}),
|
|
},
|
|
});
|
|
|
|
return { data: response.data, status: response.status };
|
|
}
|
|
|
|
// ─── CRUD Operations ───────────────────────────────────────────
|
|
|
|
/** Fetch a paginated list of entities. */
|
|
getMany<T = E[]>(config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
return this.execute<T>(DESCRIPTORS.getMany, { config });
|
|
}
|
|
|
|
/** Fetch a single entity by ID. */
|
|
getOne<T = E>(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
return this.execute<T>(DESCRIPTORS.getOne, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Create a new entity. */
|
|
create<T = E>(data: Partial<E>, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
return this.execute<T>(DESCRIPTORS.create, {
|
|
config: { ...config, data },
|
|
});
|
|
}
|
|
|
|
/** Update an existing entity by ID. */
|
|
edit<T = E>(id: string, data: Partial<E>, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
|
return this.execute<T>(DESCRIPTORS.edit, {
|
|
variableURL: { id },
|
|
config: { ...config, data },
|
|
});
|
|
}
|
|
|
|
/** Delete a single entity by ID. */
|
|
delete(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.delete, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Delete multiple entities by IDs. */
|
|
batchDelete(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchDelete, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
// ─── Activation Lifecycle ─────────────────────────────────────
|
|
|
|
/** Activate a single entity. */
|
|
activate(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.activate, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Activate multiple entities. */
|
|
batchActivate(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchActivate, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
/** Deactivate a single entity. */
|
|
deactivate(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.deactivate, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Deactivate multiple entities. */
|
|
batchDeactivate(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchDeactivate, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
// ─── Data Processing Lifecycle ────────────────────────────────
|
|
|
|
/** Confirm processing of a single data record. */
|
|
confirmProcessData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.confirmProcessData, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Confirm processing of multiple data records. */
|
|
batchConfirmProcessData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchConfirmProcessData, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
/** Cancel processing of a single data record. */
|
|
cancelProcessData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.cancelProcessData, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Cancel processing of multiple data records. */
|
|
batchCancelProcessData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchCancelProcessData, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
// ─── Transaction Lifecycle ────────────────────────────────────
|
|
|
|
/** Confirm a transaction. */
|
|
confirmProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.confirmProcessTransaction, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Confirm multiple transactions. */
|
|
batchConfirmProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchConfirmProcessTransaction, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
/** Cancel a transaction. */
|
|
cancelProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.cancelProcessTransaction, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Cancel multiple transactions. */
|
|
batchCancelProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchCancelProcessTransaction, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
/** Rollback a transaction. */
|
|
rollbackProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.rollbackProcessTransaction, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Rollback multiple transactions. */
|
|
batchRollbackProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchRollbackProcessTransaction, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
|
|
/** Hold a transaction. */
|
|
holdProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.holdProcessTransaction, {
|
|
variableURL: { id },
|
|
config,
|
|
});
|
|
}
|
|
|
|
/** Hold multiple transactions. */
|
|
batchHoldProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
|
|
return this.execute<void>(DESCRIPTORS.batchHoldProcessTransaction, {
|
|
config: { ...config, data: { ids } },
|
|
});
|
|
}
|
|
}
|