Skip to content

Commit 51383b0

Browse files
Add API classes for Actions, CRUD operations, Files, and Reports for TS SDK
1 parent 463432a commit 51383b0

21 files changed

+1697
-0
lines changed

platform/packages/sdk/README.md

Lines changed: 469 additions & 0 deletions
Large diffs are not rendered by default.

platform/packages/sdk/package.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"name": "@dynamia-tools/sdk",
3+
"version": "26.3.0",
4+
"description": "Official JavaScript / TypeScript client SDK for Dynamia Platform REST APIs",
5+
"keywords": [
6+
"dynamia",
7+
"dynamia-platform",
8+
"sdk",
9+
"rest",
10+
"typescript",
11+
"client"
12+
],
13+
"homepage": "https://www.dynamia.tools",
14+
"bugs": {
15+
"url": "https://github.com/dynamiatools/dynamiatools/issues"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "https://github.com/dynamiatools/dynamiatools.git",
20+
"directory": "framework/platform/packages/sdk"
21+
},
22+
"license": "Apache-2.0",
23+
"author": "Dynamia Soluciones IT SAS",
24+
"type": "module",
25+
"main": "./dist/index.cjs",
26+
"module": "./dist/index.js",
27+
"types": "./dist/index.d.ts",
28+
"exports": {
29+
".": {
30+
"import": {
31+
"types": "./dist/index.d.ts",
32+
"default": "./dist/index.js"
33+
},
34+
"require": {
35+
"types": "./dist/index.d.cts",
36+
"default": "./dist/index.cjs"
37+
}
38+
}
39+
},
40+
"files": [
41+
"dist",
42+
"README.md",
43+
"LICENSE"
44+
],
45+
"scripts": {
46+
"build": "vite build",
47+
"test": "vitest run",
48+
"test:watch": "vitest",
49+
"test:coverage": "vitest run --coverage",
50+
"typecheck": "tsc --noEmit",
51+
"lint": "eslint src",
52+
"clean": "rm -rf dist",
53+
"prepublishOnly": "pnpm run build && pnpm run test"
54+
},
55+
"publishConfig": {
56+
"access": "public",
57+
"registry": "https://registry.npmjs.org/"
58+
},
59+
"devDependencies": {
60+
"@types/node": "^22.0.0",
61+
"typescript": "^5.7.0",
62+
"vite": "^6.2.0",
63+
"vite-plugin-dts": "^4.5.0",
64+
"vitest": "^3.0.0",
65+
"@vitest/coverage-v8": "^3.0.0"
66+
}
67+
}
68+
69+
70+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { HttpClient } from '../http.js';
2+
import type { ActionExecutionRequest, ActionExecutionResponse } from '../types.js';
3+
4+
/**
5+
* Execute platform actions (global or entity-scoped).
6+
*/
7+
export class ActionsApi {
8+
private readonly http: HttpClient;
9+
10+
constructor(http: HttpClient) {
11+
this.http = http;
12+
}
13+
14+
/**
15+
* POST /api/app/metadata/actions/{action}
16+
* Execute a global action.
17+
*/
18+
executeGlobal(action: string, request?: ActionExecutionRequest): Promise<ActionExecutionResponse> {
19+
return this.http.post<ActionExecutionResponse>(
20+
`/api/app/metadata/actions/${encodeURIComponent(action)}`,
21+
request ?? {},
22+
);
23+
}
24+
25+
/**
26+
* POST /api/app/metadata/entities/{className}/action/{action}
27+
* Execute an entity-scoped action.
28+
*/
29+
executeEntity(
30+
className: string,
31+
action: string,
32+
request?: ActionExecutionRequest,
33+
): Promise<ActionExecutionResponse> {
34+
return this.http.post<ActionExecutionResponse>(
35+
`/api/app/metadata/entities/${encodeURIComponent(className)}/action/${encodeURIComponent(action)}`,
36+
request ?? {},
37+
);
38+
}
39+
}
40+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { HttpClient } from '../http.js';
2+
3+
type FindParams = Record<string, string | number | boolean | undefined | null>;
4+
5+
/**
6+
* Low-level generic CRUD endpoint by fully-qualified Java class name.
7+
* Base path: /crud-service/{className}
8+
*/
9+
export class CrudServiceApi<T = unknown> {
10+
private readonly http: HttpClient;
11+
private readonly basePath: string;
12+
13+
constructor(http: HttpClient, className: string) {
14+
this.http = http;
15+
this.basePath = `/crud-service/${encodeURIComponent(className)}`;
16+
}
17+
18+
/** POST/PUT /crud-service/{className} — Save (create or update depending on entity ID) */
19+
save(entity: Partial<T>): Promise<T> {
20+
return this.http.post<T>(this.basePath, entity);
21+
}
22+
23+
/** GET /crud-service/{className}/{id} — Find by ID */
24+
findById(id: string | number): Promise<T> {
25+
return this.http.get<T>(`${this.basePath}/${id}`);
26+
}
27+
28+
/** DELETE /crud-service/{className}/{id} — Delete by ID */
29+
delete(id: string | number): Promise<void> {
30+
return this.http.delete<void>(`${this.basePath}/${id}`);
31+
}
32+
33+
/** POST /crud-service/{className}/find — Find by query parameters */
34+
find(params: FindParams): Promise<T[]> {
35+
return this.http.post<T[]>(`${this.basePath}/find`, params);
36+
}
37+
38+
/** POST /crud-service/{className}/id — Get just the ID matching query parameters */
39+
getId(params: FindParams): Promise<string | number> {
40+
return this.http.post<string | number>(`${this.basePath}/id`, params);
41+
}
42+
}
43+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { HttpClient } from '../http.js';
2+
3+
/**
4+
* Download files managed by the entity-files extension.
5+
* Base path: /storage
6+
*/
7+
export class FilesApi {
8+
private readonly http: HttpClient;
9+
10+
constructor(http: HttpClient) {
11+
this.http = http;
12+
}
13+
14+
/**
15+
* GET /storage/{file}?uuid={uuid}
16+
* Download a file as a Blob.
17+
*/
18+
download(file: string, uuid: string): Promise<Blob> {
19+
return this.http.get<Blob>(`/storage/${encodeURIComponent(file)}`, { uuid });
20+
}
21+
22+
/**
23+
* Returns a direct URL for the file (useful for <img src="...">, anchor href, etc.)
24+
* No HTTP request is made.
25+
*/
26+
getUrl(file: string, uuid: string): string {
27+
return this.http.url(`/storage/${encodeURIComponent(file)}`, { uuid });
28+
}
29+
}
30+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export { MetadataApi } from './metadata.js';
2+
export { CrudResourceApi } from './crud.js';
3+
export { CrudServiceApi } from './crud-service.js';
4+
export { ActionsApi } from './actions.js';
5+
export { ReportsApi } from './reports.js';
6+
export { FilesApi } from './files.js';
7+
export { SaasApi } from './saas.js';
8+
export { ScheduleApi } from './schedule.js';
9+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { HttpClient } from '../http.js';
2+
import type {
3+
ApplicationMetadata,
4+
ApplicationMetadataActions,
5+
ApplicationMetadataEntities,
6+
EntityMetadata,
7+
NavigationTree,
8+
ViewDescriptor,
9+
ViewDescriptorMetadata,
10+
} from '../types.js';
11+
12+
/**
13+
* Provides access to application metadata endpoints.
14+
* Base path: /api/app/metadata
15+
*/
16+
export class MetadataApi {
17+
private readonly http: HttpClient;
18+
19+
constructor(http: HttpClient) {
20+
this.http = http;
21+
}
22+
23+
/** GET /api/app/metadata — Application-level metadata */
24+
getApp(): Promise<ApplicationMetadata> {
25+
return this.http.get('/api/app/metadata');
26+
}
27+
28+
/** GET /api/app/metadata/navigation — Full navigation tree */
29+
getNavigation(): Promise<NavigationTree> {
30+
return this.http.get('/api/app/metadata/navigation');
31+
}
32+
33+
/** GET /api/app/metadata/actions — All global actions */
34+
getGlobalActions(): Promise<ApplicationMetadataActions> {
35+
return this.http.get('/api/app/metadata/actions');
36+
}
37+
38+
/** GET /api/app/metadata/entities — All entity metadata */
39+
getEntities(): Promise<ApplicationMetadataEntities> {
40+
return this.http.get('/api/app/metadata/entities');
41+
}
42+
43+
/** GET /api/app/metadata/entities/{className} — Single entity metadata */
44+
getEntity(className: string): Promise<EntityMetadata> {
45+
return this.http.get(`/api/app/metadata/entities/${encodeURIComponent(className)}`);
46+
}
47+
48+
/** GET /api/app/metadata/entities/{className}/views — All view descriptors for an entity */
49+
getEntityViews(className: string): Promise<ViewDescriptorMetadata[]> {
50+
return this.http.get(`/api/app/metadata/entities/${encodeURIComponent(className)}/views`);
51+
}
52+
53+
/** GET /api/app/metadata/entities/{className}/views/{view} — Specific view descriptor */
54+
getEntityView(className: string, view: string): Promise<ViewDescriptor> {
55+
return this.http.get(
56+
`/api/app/metadata/entities/${encodeURIComponent(className)}/views/${encodeURIComponent(view)}`,
57+
);
58+
}
59+
}
60+
61+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { HttpClient } from '../http.js';
2+
import type { ReportDTO, ReportFilters } from '../types.js';
3+
4+
type ReportGetParams = Record<string, string | number | boolean | undefined | null>;
5+
6+
/**
7+
* Access the Reports extension.
8+
* Base path: /api/reports
9+
*/
10+
export class ReportsApi {
11+
private readonly http: HttpClient;
12+
13+
constructor(http: HttpClient) {
14+
this.http = http;
15+
}
16+
17+
/** GET /api/reports — List all exportable reports */
18+
list(): Promise<ReportDTO[]> {
19+
return this.http.get<ReportDTO[]>('/api/reports');
20+
}
21+
22+
/** GET /api/reports/{group}/{endpoint} — Fetch report data with query-string filters */
23+
get(group: string, endpoint: string, params?: ReportGetParams): Promise<unknown> {
24+
return this.http.get<unknown>(
25+
`/api/reports/${encodeURIComponent(group)}/${encodeURIComponent(endpoint)}`,
26+
params,
27+
);
28+
}
29+
30+
/** POST /api/reports/{group}/{endpoint} — Fetch report data with structured filters */
31+
post(group: string, endpoint: string, filters?: ReportFilters): Promise<unknown> {
32+
return this.http.post<unknown>(
33+
`/api/reports/${encodeURIComponent(group)}/${encodeURIComponent(endpoint)}`,
34+
filters,
35+
);
36+
}
37+
}
38+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { HttpClient } from '../http.js';
2+
import type { AccountDTO } from '../types.js';
3+
4+
/**
5+
* Manage multi-tenant accounts via the SaaS extension.
6+
* Base path: /api/saas
7+
*/
8+
export class SaasApi {
9+
private readonly http: HttpClient;
10+
11+
constructor(http: HttpClient) {
12+
this.http = http;
13+
}
14+
15+
/** GET /api/saas/account/{uuid} — Get account information by UUID */
16+
getAccount(uuid: string): Promise<AccountDTO> {
17+
return this.http.get<AccountDTO>(`/api/saas/account/${encodeURIComponent(uuid)}`);
18+
}
19+
}
20+

0 commit comments

Comments
 (0)