New Typescript SDK with support for CRUDs, Application Metadata and more#54
New Typescript SDK with support for CRUDs, Application Metadata and more#54marioserrano09 merged 5 commits intomainfrom
Conversation
…ces for unknown parameters
There was a problem hiding this comment.
Pull request overview
This PR introduces a new TypeScript SDK package for Dynamia Platform REST APIs (CRUD, metadata, actions, reports, files, SaaS, schedules), sets up a pnpm workspace for publishing, and adds an Astro-based demo client that consumes the SDK. It also includes a backend tweak to allow request filters to fall back to bean-property reflection when a view descriptor field is missing.
Changes:
- Added
@dynamia-tools/sdkwith typed API clients, shared HTTP/error handling, build tooling (Vite + dts), and Vitest tests. - Added
platform/packagespnpm workspace configuration and an NPM publish GitHub Action. - Added
examples/demo-client-books(Astro SSR demo) and updated backend request-filter handling.
Reviewed changes
Copilot reviewed 43 out of 45 changed files in this pull request and generated 22 comments.
Show a summary per file
| File | Description |
|---|---|
| platform/packages/tsconfig.base.json | Adds shared TS compiler defaults for packages. |
| platform/packages/sdk/vitest.config.ts | Configures Vitest and coverage for the SDK. |
| platform/packages/sdk/vite.config.ts | Configures Vite library build and type generation. |
| platform/packages/sdk/tsconfig.json | SDK TS config for dev/test typechecking. |
| platform/packages/sdk/tsconfig.build.json | SDK TS config for emitting declarations/build output. |
| platform/packages/sdk/test/client.test.ts | Adds unit tests covering client construction, CRUD, auth headers, and error handling. |
| platform/packages/sdk/src/types.ts | Defines exported SDK types (metadata, actions, CRUD envelopes/results, etc.). |
| platform/packages/sdk/src/index.ts | Defines the public SDK entrypoint exports. |
| platform/packages/sdk/src/http.ts | Implements fetch wrapper with auth headers and response parsing. |
| platform/packages/sdk/src/errors.ts | Adds SDK error type for non-2xx responses. |
| platform/packages/sdk/src/client.ts | Adds root DynamiaClient composing sub-APIs. |
| platform/packages/sdk/src/api/schedule.ts | Adds schedule endpoints wrapper. |
| platform/packages/sdk/src/api/saas.ts | Adds SaaS account endpoint wrapper. |
| platform/packages/sdk/src/api/reports.ts | Adds reports endpoints wrapper. |
| platform/packages/sdk/src/api/metadata.ts | Adds application metadata endpoints wrapper. |
| platform/packages/sdk/src/api/index.ts | Re-exports API classes for advanced usage. |
| platform/packages/sdk/src/api/files.ts | Adds file download and URL helpers. |
| platform/packages/sdk/src/api/crud.ts | Adds navigation CRUD resource wrapper and response normalization. |
| platform/packages/sdk/src/api/crud-service.ts | Adds class-name-based CRUD service wrapper. |
| platform/packages/sdk/src/api/actions.ts | Adds global/entity action execution wrapper. |
| platform/packages/sdk/package.json | Defines SDK package metadata, exports map, scripts, and dev deps. |
| platform/packages/sdk/README.md | Adds SDK documentation and usage examples. |
| platform/packages/pnpm-workspace.yaml | Declares pnpm workspace packages. |
| platform/packages/pnpm-lock.yaml | Locks dependencies for the packages workspace. |
| platform/packages/package.json | Adds workspace root package with shared tooling scripts/deps. |
| platform/packages/README.md | Documents the packages workspace and intended package set. |
| platform/packages/.npmrc | Sets NPM publish defaults (registry/access). |
| platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationQuerySupport.java | Adds reflective fallback for request filters when descriptor field is absent. |
| examples/demo-client-books/tsconfig.json | Configures TS for the Astro demo. |
| examples/demo-client-books/src/pages/index.astro | Adds demo homepage using SDK-driven CRUD queries. |
| examples/demo-client-books/src/pages/categories/index.astro | Adds categories listing page. |
| examples/demo-client-books/src/pages/books/index.astro | Adds books listing with filters/pagination. |
| examples/demo-client-books/src/pages/books/[id].astro | Adds book detail page and error handling. |
| examples/demo-client-books/src/lib/types.ts | Adds demo domain types. |
| examples/demo-client-books/src/lib/client.ts | Creates a demo DynamiaClient instance from env config. |
| examples/demo-client-books/src/lib/api.ts | Adds demo data-access helpers on top of the SDK. |
| examples/demo-client-books/src/layouts/Layout.astro | Adds shared layout and styling for the demo. |
| examples/demo-client-books/package.json | Adds Astro demo package config and local SDK dependency. |
| examples/demo-client-books/astro.config.mjs | Configures Astro SSR node adapter. |
| examples/demo-client-books/README.md | Documents how to run and what the demo includes. |
| examples/demo-client-books/.gitignore | Ignores build artifacts and env files. |
| examples/demo-client-books/.env.example | Provides example backend URL env var. |
| .github/workflows/release.yml | Adjusts release trigger type. |
| .github/workflows/publish-npm.yml | Adds workflow to build/test/publish packages on release publish. |
Files not reviewed (1)
- platform/packages/pnpm-lock.yaml: Language not supported
| Field field = descriptor.getField(paramName); | ||
| if (field == null) { | ||
| return; | ||
| try { | ||
| var property = ObjectOperations.getPropertyInfo(descriptor.getBeanClass(), paramName); | ||
| if (property != null) { | ||
| field = new Field(paramName, property.getType()); | ||
| } else { | ||
| return; | ||
| } | ||
| }catch (Exception e) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
This change makes request filters apply to any bean property (via reflection) even when the field is not present in the ViewDescriptor. That’s a behavior change from the documented/previous contract (“fields not present in the descriptor are ignored”) and may allow filtering on properties that weren’t intended to be queryable. Consider keeping the descriptor as the allowlist (or gating the reflective fallback behind an explicit opt-in), and update the Javadoc accordingly.
| export interface CrudRawResponse<T = unknown> { | ||
| /** The page records */ | ||
| data: T[]; | ||
| /** | ||
| * Pagination metadata. `null` when the response is not paginated | ||
| * (`@JsonInclude(JsonInclude.Include.NON_NULL)` in Java — field may be absent from JSON). | ||
| */ | ||
| pageable: CrudPageable | null; | ||
| /** Status string, typically `"OK"` */ | ||
| response: string; |
There was a problem hiding this comment.
CrudRawResponse.pageable is documented as potentially absent from JSON (due to @JsonInclude(NON_NULL)), but the type requires the property to be present (pageable: CrudPageable | null). To match real payloads and avoid forcing consumers to fabricate pageable, consider making it optional (pageable?: CrudPageable | null).
| return this.http.get<T>(`${this.basePath}/${id}`); | ||
| } | ||
|
|
||
| /** DELETE /crud-service/{className}/{id} — Delete by ID */ | ||
| delete(id: string | number): Promise<void> { | ||
| return this.http.delete<void>(`${this.basePath}/${id}`); |
There was a problem hiding this comment.
findById() interpolates id into the URL without encoding. If IDs can be strings (as your signature allows), reserved URL characters can break routing. Encode the id segment (e.g. with encodeURIComponent).
| return this.http.get<T>(`${this.basePath}/${id}`); | |
| } | |
| /** DELETE /crud-service/{className}/{id} — Delete by ID */ | |
| delete(id: string | number): Promise<void> { | |
| return this.http.delete<void>(`${this.basePath}/${id}`); | |
| return this.http.get<T>(`${this.basePath}/${encodeURIComponent(String(id))}`); | |
| } | |
| /** DELETE /crud-service/{className}/{id} — Delete by ID */ | |
| delete(id: string | number): Promise<void> { | |
| return this.http.delete<void>(`${this.basePath}/${encodeURIComponent(String(id))}`); |
| const PAGE_SIZE = 6; | ||
|
|
||
| const [booksResult, categoriesResult] = await Promise.all([ | ||
| getBooks({ page, pageSize: PAGE_SIZE, ...(categoryId ? { 'category.id': categoryId } : {}) }), |
There was a problem hiding this comment.
The REST navigation API expects the pagination parameter to be size (see RestNavigationReadOperation), but this page passes pageSize. As a result pagination won't be applied server-side. Use size: PAGE_SIZE instead of pageSize: PAGE_SIZE when calling getBooks.
| getBooks({ page, pageSize: PAGE_SIZE, ...(categoryId ? { 'category.id': categoryId } : {}) }), | |
| getBooks({ page, size: PAGE_SIZE, ...(categoryId ? { 'category.id': categoryId } : {}) }), |
|
|
||
| export async function getBookReviews(bookId: number | string): Promise<BookReview[]> { | ||
| const result = await client.crud<BookReview>('library/books') | ||
| .findAll({ 'book.id': bookId, pageSize: 50 }) |
There was a problem hiding this comment.
findAll({ 'book.id': bookId, pageSize: 50 }) uses pageSize, but the backend expects the pagination param to be size. With pageSize, the server will likely ignore the requested size and use its default. Use size: 50 here.
| .findAll({ 'book.id': bookId, pageSize: 50 }) | |
| .findAll({ 'book.id': bookId, size: 50 }) |
| /** PUT /api/{path}/{id} — Full update */ | ||
| update(id: string | number, entity: Partial<T>): Promise<T> { | ||
| return this.http.put<T>(`${this.basePath}/${id}`, entity); | ||
| } |
There was a problem hiding this comment.
update() interpolates id into the URL without encoding, which can break requests for string IDs containing reserved URL characters. Encode the id path segment before concatenating.
| /** DELETE /api/{path}/{id} — Delete */ | ||
| delete(id: string | number): Promise<void> { | ||
| return this.http.delete<void>(`${this.basePath}/${id}`); | ||
| } |
There was a problem hiding this comment.
delete() interpolates id into the URL without encoding. For non-numeric/string IDs this can produce an invalid path or unintended routing. Encode the id path segment when constructing the URL.
| "default": "./dist/index.js" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/index.d.cts", |
There was a problem hiding this comment.
exports.require.types points to ./dist/index.d.cts, but the build (vite-plugin-dts/tsc) typically emits index.d.ts and may not generate .d.cts. If index.d.cts isn't produced, CommonJS consumers will fail to resolve types. Either generate the .d.cts file during build or point both import and require types to ./dist/index.d.ts.
| "types": "./dist/index.d.cts", | |
| "types": "./dist/index.d.ts", |
| const [booksResult, categoriesResult] = await Promise.all([ | ||
| getBooks({ | ||
| page, | ||
| pageSize: PAGE_SIZE, |
There was a problem hiding this comment.
The REST navigation API uses page + size for pagination. This page sends pageSize, so the backend will fall back to its default page size and ignore your intended PAGE_SIZE. Switch to size: PAGE_SIZE in the getBooks(...) call.
| pageSize: PAGE_SIZE, | |
| size: PAGE_SIZE, |
| node-version: '24' | ||
| registry-url: 'https://registry.npmjs.org' | ||
| cache: 'pnpm' | ||
| cache-dependency-path: framework/platform/packages/pnpm-lock.yaml |
There was a problem hiding this comment.
cache-dependency-path points to framework/platform/packages/pnpm-lock.yaml, but this repo's lockfile is at platform/packages/pnpm-lock.yaml (no framework/ directory). This will disable pnpm caching and may slow down the workflow; update the path to the correct lockfile location.
| cache-dependency-path: framework/platform/packages/pnpm-lock.yaml | |
| cache-dependency-path: platform/packages/pnpm-lock.yaml |
No description provided.