Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
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 } },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { BaseEntity } from './types';
|
||||
import { BaseRemoteDataServices } from './base-remote.data-services';
|
||||
|
||||
/**
|
||||
* General-purpose remote data services.
|
||||
*
|
||||
* A concrete, non-abstract version of BaseRemoteDataServices that
|
||||
* can be instantiated directly for standard CRUD modules that don't
|
||||
* need additional custom methods.
|
||||
*
|
||||
* For modules requiring domain-specific operations beyond standard
|
||||
* CRUD + lifecycle, extend BaseRemoteDataServices instead and add
|
||||
* custom methods using `this.execute()` or `this.customRequest()`.
|
||||
*
|
||||
* @typeParam E - The domain entity type
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Direct instantiation for standard modules
|
||||
* const bookingServices = new CommonRemoteDataServices<BookingEntity>(
|
||||
* apiClient,
|
||||
* { apiUrl: '/bookings', moduleKey: 'BOOKING' },
|
||||
* );
|
||||
*
|
||||
* const { data } = await bookingServices.getMany();
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // For modules needing custom operations, extend the base:
|
||||
* class InvoiceDataServices extends BaseRemoteDataServices<InvoiceEntity> {
|
||||
* async calculateTax(invoiceId: string) {
|
||||
* return this.customRequest<TaxResult>({
|
||||
* url: `/invoices/${invoiceId}/calculate-tax`,
|
||||
* method: 'POST',
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class CommonRemoteDataServices<
|
||||
E extends BaseEntity = BaseEntity,
|
||||
> extends BaseRemoteDataServices<E> {}
|
||||
@@ -0,0 +1,126 @@
|
||||
import type { RequestMethodMap, RequestDescriptor, ApiURLMap } from './types';
|
||||
|
||||
// ─── Request Actions ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Constants for the 'ex-module-action' header.
|
||||
* Maps to backend permission/audit checks.
|
||||
*/
|
||||
export const REQUEST_ACTION = {
|
||||
VIEW: 'VIEW',
|
||||
CREATE: 'CREATE',
|
||||
EDIT: 'EDIT',
|
||||
DELETE: 'DELETE',
|
||||
CONFIRM_DATA: 'CONFIRM_DATA',
|
||||
CANCEL_DATA: 'CANCEL_DATA',
|
||||
CONFIRM_PROCESS_TRANSACTION: 'CONFIRM_PROCESS_TRANSACTION',
|
||||
CANCEL_PROCESS_TRANSACTION: 'CANCEL_PROCESS_TRANSACTION',
|
||||
} as const;
|
||||
|
||||
// ─── Default HTTP Methods ───────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Sensible REST defaults for all operations.
|
||||
* Can be overridden per data-services instance.
|
||||
*/
|
||||
export const DEFAULT_METHODS: RequestMethodMap = {
|
||||
getManyMethod: 'GET',
|
||||
getOneMethod: 'GET',
|
||||
createMethod: 'POST',
|
||||
editMethod: 'PUT',
|
||||
deleteMethod: 'DELETE',
|
||||
batchDeleteMethod: 'POST',
|
||||
|
||||
activateMethod: 'PATCH',
|
||||
batchActivateMethod: 'POST',
|
||||
deactivateMethod: 'PATCH',
|
||||
batchDeactivateMethod: 'POST',
|
||||
|
||||
confirmProcessDataMethod: 'PATCH',
|
||||
batchConfirmProcessDataMethod: 'POST',
|
||||
cancelProcessDataMethod: 'PATCH',
|
||||
batchCancelProcessDataMethod: 'POST',
|
||||
|
||||
confirmProcessTransactionMethod: 'PATCH',
|
||||
batchConfirmProcessTransactionMethod: 'POST',
|
||||
cancelProcessTransactionMethod: 'PATCH',
|
||||
batchCancelProcessTransactionMethod: 'POST',
|
||||
rollbackProcessTransactionMethod: 'PATCH',
|
||||
batchRollbackProcessTransactionMethod: 'POST',
|
||||
holdProcessTransactionMethod: 'PATCH',
|
||||
batchHoldProcessTransactionMethod: 'POST',
|
||||
};
|
||||
|
||||
// ─── Operation Descriptors ──────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Pre-defined descriptors for all standard operations.
|
||||
* Each descriptor maps an operation to its URL key, method key,
|
||||
* and action header — eliminating 22 boilerplate methods.
|
||||
*/
|
||||
export const DESCRIPTORS = {
|
||||
getMany: { urlKey: 'getManyUrl', methodKey: 'getManyMethod', action: REQUEST_ACTION.VIEW },
|
||||
getOne: { urlKey: 'getOneUrl', methodKey: 'getOneMethod', action: REQUEST_ACTION.VIEW },
|
||||
create: { urlKey: 'createUrl', methodKey: 'createMethod', action: REQUEST_ACTION.CREATE },
|
||||
edit: { urlKey: 'editUrl', methodKey: 'editMethod', action: REQUEST_ACTION.EDIT },
|
||||
delete: { urlKey: 'deleteUrl', methodKey: 'deleteMethod', action: REQUEST_ACTION.DELETE },
|
||||
batchDelete: { urlKey: 'batchDeleteUrl', methodKey: 'batchDeleteMethod', action: REQUEST_ACTION.DELETE },
|
||||
|
||||
activate: { urlKey: 'activateUrl', methodKey: 'activateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
|
||||
batchActivate: { urlKey: 'batchActivateUrl', methodKey: 'batchActivateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
|
||||
deactivate: { urlKey: 'deactivateUrl', methodKey: 'deactivateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
|
||||
batchDeactivate: { urlKey: 'batchDeactivateUrl', methodKey: 'batchDeactivateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
|
||||
|
||||
confirmProcessData: { urlKey: 'confirmProcessDataUrl', methodKey: 'confirmProcessDataMethod', action: REQUEST_ACTION.CONFIRM_DATA },
|
||||
batchConfirmProcessData: { urlKey: 'batchConfirmProcessDataUrl', methodKey: 'batchConfirmProcessDataMethod', action: REQUEST_ACTION.CONFIRM_DATA },
|
||||
cancelProcessData: { urlKey: 'cancelProcessDataUrl', methodKey: 'cancelProcessDataMethod', action: REQUEST_ACTION.CANCEL_DATA },
|
||||
batchCancelProcessData: { urlKey: 'batchCancelProcessDataUrl', methodKey: 'batchCancelProcessDataMethod', action: REQUEST_ACTION.CANCEL_DATA },
|
||||
|
||||
confirmProcessTransaction: { urlKey: 'confirmProcessTransactionUrl', methodKey: 'confirmProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
|
||||
batchConfirmProcessTransaction: { urlKey: 'batchConfirmProcessTransactionUrl', methodKey: 'batchConfirmProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
|
||||
cancelProcessTransaction: { urlKey: 'cancelProcessTransactionUrl', methodKey: 'cancelProcessTransactionMethod', action: REQUEST_ACTION.CANCEL_PROCESS_TRANSACTION },
|
||||
batchCancelProcessTransaction: { urlKey: 'batchCancelProcessTransactionUrl', methodKey: 'batchCancelProcessTransactionMethod', action: REQUEST_ACTION.CANCEL_PROCESS_TRANSACTION },
|
||||
rollbackProcessTransaction: { urlKey: 'rollbackProcessTransactionUrl', methodKey: 'rollbackProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
|
||||
batchRollbackProcessTransaction: { urlKey: 'batchRollbackProcessTransactionUrl', methodKey: 'batchRollbackProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
|
||||
holdProcessTransaction: { urlKey: 'holdProcessTransactionUrl', methodKey: 'holdProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
|
||||
batchHoldProcessTransaction: { urlKey: 'batchHoldProcessTransactionUrl', methodKey: 'batchHoldProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
|
||||
} as const satisfies Record<string, RequestDescriptor>;
|
||||
|
||||
// ─── Default URL Factory ────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Generates the complete API URL map from a base path.
|
||||
*
|
||||
* @param apiUrl - Base API path (e.g., '/bookings')
|
||||
* @returns Full ApiURLMap with all CRUD + lifecycle URLs
|
||||
*/
|
||||
export function makeDefaultURLs(apiUrl: string): ApiURLMap {
|
||||
return {
|
||||
getManyUrl: `${apiUrl}`,
|
||||
getOneUrl: `${apiUrl}/:id`,
|
||||
createUrl: `${apiUrl}`,
|
||||
editUrl: `${apiUrl}/:id`,
|
||||
|
||||
deleteUrl: `${apiUrl}/:id`,
|
||||
batchDeleteUrl: `${apiUrl}/batch-delete`,
|
||||
|
||||
activateUrl: `${apiUrl}/:id/active`,
|
||||
batchActivateUrl: `${apiUrl}/batch-active`,
|
||||
deactivateUrl: `${apiUrl}/:id/inactive`,
|
||||
batchDeactivateUrl: `${apiUrl}/batch-inactive`,
|
||||
|
||||
confirmProcessDataUrl: `${apiUrl}/:id/confirm`,
|
||||
batchConfirmProcessDataUrl: `${apiUrl}/batch-confirm`,
|
||||
cancelProcessDataUrl: `${apiUrl}/:id/cancel`,
|
||||
batchCancelProcessDataUrl: `${apiUrl}/batch-cancel`,
|
||||
|
||||
confirmProcessTransactionUrl: `${apiUrl}/:id/confirm-data`,
|
||||
batchConfirmProcessTransactionUrl: `${apiUrl}/batch-confirm-data`,
|
||||
cancelProcessTransactionUrl: `${apiUrl}/:id/cancel`,
|
||||
batchCancelProcessTransactionUrl: `${apiUrl}/batch-cancel`,
|
||||
rollbackProcessTransactionUrl: `${apiUrl}/:id/confirm-rollback`,
|
||||
batchRollbackProcessTransactionUrl: `${apiUrl}/batch-confirm-rollback`,
|
||||
holdProcessTransactionUrl: `${apiUrl}/:id/confirm-hold`,
|
||||
batchHoldProcessTransactionUrl: `${apiUrl}/batch-confirm-hold`,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// ─── Classes ────────────────────────────────────────────────────
|
||||
export { BaseRemoteDataServices } from './base-remote.data-services';
|
||||
export { CommonRemoteDataServices } from './common-remote.data-services';
|
||||
|
||||
// ─── Utilities ──────────────────────────────────────────────────
|
||||
export { interpolateUrl } from './url-builder';
|
||||
|
||||
// ─── Constants ──────────────────────────────────────────────────
|
||||
export { REQUEST_ACTION, DEFAULT_METHODS, DESCRIPTORS, makeDefaultURLs } from './constants';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────
|
||||
export type {
|
||||
BaseEntity,
|
||||
ApiURLMap,
|
||||
RequestMethodMap,
|
||||
RequestDescriptor,
|
||||
ExecuteOptions,
|
||||
DataServicesConfig,
|
||||
} from './types';
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import type { TelemetryContext } from '../http-client/types';
|
||||
|
||||
// ─── Base Entity ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Minimal entity contract. All domain entities must have
|
||||
* an optional `id` field for CRUD operations.
|
||||
*/
|
||||
export interface BaseEntity {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
// ─── API URL Map ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Complete URL map for all standard CRUD and lifecycle operations.
|
||||
* Each key maps to a URL template string (e.g., '/bookings/:id').
|
||||
*/
|
||||
export interface ApiURLMap {
|
||||
getManyUrl: string;
|
||||
getOneUrl: string;
|
||||
createUrl: string;
|
||||
editUrl: string;
|
||||
deleteUrl: string;
|
||||
batchDeleteUrl: string;
|
||||
|
||||
activateUrl: string;
|
||||
batchActivateUrl: string;
|
||||
deactivateUrl: string;
|
||||
batchDeactivateUrl: string;
|
||||
|
||||
confirmProcessDataUrl: string;
|
||||
batchConfirmProcessDataUrl: string;
|
||||
cancelProcessDataUrl: string;
|
||||
batchCancelProcessDataUrl: string;
|
||||
|
||||
confirmProcessTransactionUrl: string;
|
||||
batchConfirmProcessTransactionUrl: string;
|
||||
cancelProcessTransactionUrl: string;
|
||||
batchCancelProcessTransactionUrl: string;
|
||||
rollbackProcessTransactionUrl: string;
|
||||
batchRollbackProcessTransactionUrl: string;
|
||||
holdProcessTransactionUrl: string;
|
||||
batchHoldProcessTransactionUrl: string;
|
||||
}
|
||||
|
||||
// ─── HTTP Method Map ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* HTTP method overrides for each operation.
|
||||
* Defaults to sensible REST conventions (GET, POST, PUT, DELETE, PATCH).
|
||||
*/
|
||||
export interface RequestMethodMap {
|
||||
getManyMethod: string;
|
||||
getOneMethod: string;
|
||||
createMethod: string;
|
||||
editMethod: string;
|
||||
deleteMethod: string;
|
||||
batchDeleteMethod: string;
|
||||
|
||||
activateMethod: string;
|
||||
batchActivateMethod: string;
|
||||
deactivateMethod: string;
|
||||
batchDeactivateMethod: string;
|
||||
|
||||
confirmProcessDataMethod: string;
|
||||
batchConfirmProcessDataMethod: string;
|
||||
cancelProcessDataMethod: string;
|
||||
batchCancelProcessDataMethod: string;
|
||||
|
||||
confirmProcessTransactionMethod: string;
|
||||
batchConfirmProcessTransactionMethod: string;
|
||||
cancelProcessTransactionMethod: string;
|
||||
batchCancelProcessTransactionMethod: string;
|
||||
rollbackProcessTransactionMethod: string;
|
||||
batchRollbackProcessTransactionMethod: string;
|
||||
holdProcessTransactionMethod: string;
|
||||
batchHoldProcessTransactionMethod: string;
|
||||
}
|
||||
|
||||
// ─── Request Descriptor ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Describes a single operation in terms of its URL, method, and
|
||||
* action header. Used by the generic `execute()` method.
|
||||
*/
|
||||
export interface RequestDescriptor {
|
||||
/** Key into the ApiURLMap. */
|
||||
urlKey: keyof ApiURLMap;
|
||||
/** Key into the RequestMethodMap. */
|
||||
methodKey: keyof RequestMethodMap;
|
||||
/** Value for the 'ex-module-action' header. */
|
||||
action: string;
|
||||
}
|
||||
|
||||
// ─── Execute Options ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Options passed to the generic `execute()` method.
|
||||
*/
|
||||
export interface ExecuteOptions {
|
||||
/** Dynamic URL parameters (e.g., `{ id: '42' }`). */
|
||||
variableURL?: Record<string, string>;
|
||||
/** Additional Axios request config (params, data, headers, etc). */
|
||||
config?: AxiosRequestConfig;
|
||||
/** Per-request telemetry context for custom spans, tags, events. */
|
||||
telemetryContext?: TelemetryContext;
|
||||
}
|
||||
|
||||
// ─── Data Services Constructor ──────────────────────────────────
|
||||
|
||||
/**
|
||||
* Configuration for constructing a BaseRemoteDataServices instance.
|
||||
*/
|
||||
export interface DataServicesConfig {
|
||||
/** Base API path (e.g., '/bookings'). Used to generate all URL templates. */
|
||||
apiUrl?: string;
|
||||
/** Module key for the 'ex-module-key' header (e.g., 'BOOKING'). */
|
||||
moduleKey?: string;
|
||||
/** Override specific URL templates. */
|
||||
urls?: Partial<ApiURLMap>;
|
||||
/** Override specific HTTP methods. */
|
||||
methods?: Partial<RequestMethodMap>;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Interpolates dynamic URL parameters.
|
||||
*
|
||||
* Replaces `:paramName` segments in a URL template with values
|
||||
* from the provided variables object.
|
||||
*
|
||||
* @param template - URL template (e.g., '/bookings/:id/confirm')
|
||||
* @param variables - Key-value map of parameter names to values
|
||||
* @returns The interpolated URL string
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* interpolateUrl('/bookings/:id/confirm', { id: '42' });
|
||||
* // => '/bookings/42/confirm'
|
||||
*
|
||||
* interpolateUrl('/orgs/:orgId/users/:userId', { orgId: 'a', userId: 'b' });
|
||||
* // => '/orgs/a/users/b'
|
||||
* ```
|
||||
*/
|
||||
export function interpolateUrl(
|
||||
template: string,
|
||||
variables?: Record<string, string>,
|
||||
): string {
|
||||
if (!variables || Object.keys(variables).length === 0) {
|
||||
return template;
|
||||
}
|
||||
|
||||
return template
|
||||
.split('/')
|
||||
.map((segment) => {
|
||||
if (segment.startsWith(':')) {
|
||||
const key = segment.slice(1);
|
||||
const value = variables[key];
|
||||
if (value === undefined) {
|
||||
throw new Error(
|
||||
`[interpolateUrl] Missing value for URL parameter ":${key}" in template "${template}"`,
|
||||
);
|
||||
}
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
return segment;
|
||||
})
|
||||
.join('/');
|
||||
}
|
||||
Reference in New Issue
Block a user