|
| 1 | +import type { HttpClient } from '../http.js'; |
| 2 | +import type { CrudListResult, CrudQueryParams, CrudRawResponse } from '../types.js'; |
| 3 | + |
| 4 | +/** |
| 5 | + * CRUD operations for a single CrudPage resource (navigation-based endpoints). |
| 6 | + * Base path: /api/{virtualPath} |
| 7 | + * |
| 8 | + * The server returns: |
| 9 | + * { data: T[], pageable: { page, pageSize, totalSize, pagesNumber, ... }, response: "OK" } |
| 10 | + * The SDK normalises this into CrudListResult<T>. |
| 11 | + */ |
| 12 | +export class CrudResourceApi<T = unknown> { |
| 13 | + private readonly http: HttpClient; |
| 14 | + private readonly basePath: string; |
| 15 | + |
| 16 | + constructor(http: HttpClient, virtualPath: string) { |
| 17 | + this.http = http; |
| 18 | + // Normalise: strip leading slash so we can always prepend /api/ |
| 19 | + const clean = virtualPath.replace(/^\/+/, ''); |
| 20 | + this.basePath = `/api/${clean}`; |
| 21 | + } |
| 22 | + |
| 23 | + /** GET /api/{path} — List all (with optional pagination / filter params) */ |
| 24 | + async findAll(params?: CrudQueryParams): Promise<CrudListResult<T>> { |
| 25 | + const raw = await this.http.get<CrudRawResponse<T>>( |
| 26 | + this.basePath, |
| 27 | + params as Record<string, string | number | boolean | undefined | null>, |
| 28 | + ); |
| 29 | + return normaliseCrudResponse(raw); |
| 30 | + } |
| 31 | + |
| 32 | + /** GET /api/{path}/{id} — Get by ID */ |
| 33 | + findById(id: string | number): Promise<T> { |
| 34 | + return this.http.get<T>(`${this.basePath}/${id}`); |
| 35 | + } |
| 36 | + |
| 37 | + /** POST /api/{path} — Create */ |
| 38 | + create(entity: Partial<T>): Promise<T> { |
| 39 | + return this.http.post<T>(this.basePath, entity); |
| 40 | + } |
| 41 | + |
| 42 | + /** PUT /api/{path}/{id} — Full update */ |
| 43 | + update(id: string | number, entity: Partial<T>): Promise<T> { |
| 44 | + return this.http.put<T>(`${this.basePath}/${id}`, entity); |
| 45 | + } |
| 46 | + |
| 47 | + /** DELETE /api/{path}/{id} — Delete */ |
| 48 | + delete(id: string | number): Promise<void> { |
| 49 | + return this.http.delete<void>(`${this.basePath}/${id}`); |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +// ── helpers ────────────────────────────────────────────────────────────────── |
| 54 | + |
| 55 | +/** |
| 56 | + * Map the raw `RestNavigationContext.ListResult` envelope to the normalised `CrudListResult`. |
| 57 | + * |
| 58 | + * - `pageable` may be `null` (annotated `@JsonInclude(NON_NULL)` in Java) when the |
| 59 | + * endpoint returns a flat, non-paginated list. |
| 60 | + * - Defensive: also handles the already-normalised flat format (e.g. test mocks). |
| 61 | + */ |
| 62 | +function normaliseCrudResponse<T>(raw: CrudRawResponse<T> | CrudListResult<T>): CrudListResult<T> { |
| 63 | + // Already normalised (e.g. mocked in tests with flat format) |
| 64 | + if ('content' in raw && Array.isArray((raw as CrudListResult<T>).content)) { |
| 65 | + return raw as CrudListResult<T>; |
| 66 | + } |
| 67 | + |
| 68 | + const r = raw as CrudRawResponse<T>; |
| 69 | + const p = r.pageable; // may be null — @JsonInclude(NON_NULL) |
| 70 | + |
| 71 | + return { |
| 72 | + content: r.data ?? [], |
| 73 | + total: p?.totalSize ?? (r.data?.length ?? 0), |
| 74 | + page: p?.page ?? 1, |
| 75 | + pageSize: p?.pageSize ?? (r.data?.length ?? 0), |
| 76 | + totalPages: p?.pagesNumber ?? 1, |
| 77 | + }; |
| 78 | +} |
| 79 | + |
0 commit comments