ra-spring-data-provider 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # ra-spring-data-provider
2
+
3
+ Te [React Admin](https://marmelab.com/react-admin/) data provider of [ra-spring-json-server].
4
+
5
+ This package provides a data provider that follows JSON Server API conventions, specifically adapted for Spring Boot backends. It supports efficient bulk operations and is designed to work seamlessly with Spring Boot controllers implementing the `IRAController` interface from [ra-spring-json-server] library.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install ra-spring-data-provider
11
+ # or
12
+ yarn add ra-spring-data-provider
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```jsx
18
+ import * as React from "react";
19
+ import { Admin, Resource } from "react-admin";
20
+ import raSpringDataProvider from "ra-spring-data-provider";
21
+
22
+ const dataProvider = jsonServerProvider("http://localhost:8080/api");
23
+
24
+ const App = () => (
25
+ <Admin dataProvider={dataProvider}>
26
+ <Resource name="users" list={ListGuesser} />
27
+ </Admin>
28
+ );
29
+
30
+ export default App;
31
+ ```
32
+
33
+ ## API Mapping
34
+
35
+ This data provider uses the JSON Server API format to communicate with the backend. Your Spring Boot API should follow these conventions:
36
+
37
+ | React Admin Method | HTTP Method | URL Example |
38
+ | ------------------ | ----------- | ------------------------------------------------------------- |
39
+ | `getList` | `GET` | `http://api.url/users?_sort=name&_order=ASC&_start=0&_end=24` |
40
+ | `getOne` | `GET` | `http://api.url/users/123` |
41
+ | `getMany` | `GET` | `http://api.url/users?id=123&id=456` |
42
+ | `getManyReference` | `GET` | `http://api.url/users?authorId=345` |
43
+ | `create` | `POST` | `http://api.url/users` |
44
+ | `update` | `PUT` | `http://api.url/users/123` |
45
+ | `updateMany` | `PUT` | `http://api.url/users?id=123&id=456` |
46
+ | `delete` | `DELETE` | `http://api.url/users/123` |
47
+ | `deleteMany` | `DELETE` | `http://api.url/users?id=123&id=456` |
48
+
49
+ ## Backend Requirements
50
+
51
+ For the Spring Boot backend implementation, use the **[ra-spring-json-server]** library which provides all the necessary endpoints and configurations to work with this data provider.
52
+
53
+ ## Development
54
+
55
+ ### Running Integration Tests
56
+
57
+ You can run the complete integration test suite from the project root:
58
+
59
+ ```bash
60
+ cd .. && ./run-integration-tests.sh
61
+ ```
62
+
63
+ This script will start the Spring Boot backend, run the tests, and clean up automatically.
64
+
65
+ ## Test Cases
66
+
67
+ The tests cover:
68
+
69
+ - ✅ Display users list
70
+ - ✅ Create a new user
71
+ - ✅ Edit an existing user
72
+ - ✅ Delete a user
73
+ - ✅ Filter/search users
74
+ - ✅ Sort users
75
+
76
+ ## Requirements
77
+
78
+ - Node.js 18+
79
+ - Java 17+
80
+
81
+ [ra-spring-json-server]: https://github.com/femrek/ra-spring-json-server
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "ra-spring-data-provider",
3
+ "version": "1.0.1",
4
+ "description": "React Admin data provider for Spring Boot REST APIs",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "test": "playwright test",
9
+ "test:ui": "playwright test --ui",
10
+ "test:headed": "playwright test --headed",
11
+ "test:debug": "playwright test --debug",
12
+ "test:users": "playwright test users.spec.js",
13
+ "test:data-provider": "playwright test data-provider.spec.js",
14
+ "test:error-handling": "playwright test error-handling.spec.js",
15
+ "test:performance": "playwright test performance.spec.js",
16
+ "test:ui-ux": "playwright test ui-ux.spec.js",
17
+ "test:report": "playwright show-report",
18
+ "install-browsers": "playwright install",
19
+ "release": "release-it"
20
+ },
21
+ "keywords": [
22
+ "react-admin",
23
+ "data-provider",
24
+ "spring-boot",
25
+ "rest-api"
26
+ ],
27
+ "main": "src/index.js",
28
+ "module": "src/index.js",
29
+ "files": [
30
+ "src",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/femrek/ra-spring-json-server.git",
37
+ "directory": "ra-spring-data-provider"
38
+ },
39
+ "author": "femrek",
40
+ "license": "Apache-2.0",
41
+ "dependencies": {
42
+ "query-string": "^9.1.0",
43
+ "ra-core": "^5.4.0",
44
+ "ra-data-json-server": "^5.13.6"
45
+ },
46
+ "peerDependencies": {
47
+ "react": "^18.3.1",
48
+ "react-admin": "^5.4.0",
49
+ "react-dom": "^18.3.1"
50
+ },
51
+ "devDependencies": {
52
+ "@emotion/react": "^11.13.3",
53
+ "@emotion/styled": "^11.13.0",
54
+ "@mui/icons-material": "^6.3.0",
55
+ "@mui/material": "^6.3.0",
56
+ "@playwright/experimental-ct-react": "^1.49.0",
57
+ "@playwright/test": "^1.49.0",
58
+ "@types/node": "^25.0.10",
59
+ "@vitejs/plugin-react": "^4.3.4",
60
+ "react": "^18.3.1",
61
+ "react-admin": "^5.4.0",
62
+ "react-dom": "^18.3.1",
63
+ "release-it": "^19.2.4",
64
+ "vite": "^6.0.5"
65
+ }
66
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { DataProvider } from "ra-core";
2
+
3
+ /**
4
+ * Creates a React Admin data provider for Spring Boot REST APIs
5
+ *
6
+ * @param apiUrl - The base URL of your Spring Boot API (e.g., 'http://localhost:8081/api')
7
+ * @param httpClient - Optional custom HTTP client function (defaults to fetchUtils.fetchJson)
8
+ * @returns A React Admin DataProvider configured for Spring Boot
9
+ *
10
+ * @example
11
+ * import raSpringDataProvider from 'ra-spring-data-provider';
12
+ *
13
+ * const dataProvider = raSpringDataProvider('http://localhost:8081/api');
14
+ *
15
+ * const App = () => (
16
+ * <Admin dataProvider={dataProvider}>
17
+ * <Resource name="users" list={UserList} />
18
+ * </Admin>
19
+ * );
20
+ */
21
+ declare const raSpringDataProvider: (
22
+ apiUrl: string,
23
+ httpClient?: (
24
+ url: string,
25
+ options?: any,
26
+ ) => Promise<{ headers: Headers; json: any }>,
27
+ ) => DataProvider;
28
+
29
+ export default raSpringDataProvider;
package/src/index.ts ADDED
@@ -0,0 +1,191 @@
1
+ import queryString from "query-string";
2
+ import { fetchUtils, DataProvider } from "ra-core";
3
+
4
+ /**
5
+ * Creates a React Admin data provider for Spring Boot REST APIs following JSON Server conventions.
6
+ *
7
+ * This data provider is designed to work with Spring Boot controllers that implement the
8
+ * IRAController interface, supporting JSON Server-style query parameters and response formats.
9
+ *
10
+ * @param apiUrl - The base URL of your Spring Boot API (e.g., 'http://localhost:8081/api')
11
+ * @param httpClient - Optional custom HTTP client function (defaults to fetchUtils.fetchJson)
12
+ *
13
+ * @returns A React Admin DataProvider instance
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * import { Admin, Resource } from 'react-admin';
18
+ * import raSpringDataProvider from 'ra-spring-data-provider';
19
+ *
20
+ * const dataProvider = raSpringDataProvider('http://localhost:8081/api');
21
+ *
22
+ * const App = () => (
23
+ * <Admin dataProvider={dataProvider}>
24
+ * <Resource name="users" list={UserList} edit={UserEdit} create={UserCreate} />
25
+ * </Admin>
26
+ * );
27
+ * ```
28
+ *
29
+ * @remarks
30
+ * **API Requirements:**
31
+ * - GET endpoints must return X-Total-Count header for pagination
32
+ * - List queries use _start, _end, _sort, _order query parameters
33
+ * - Bulk operations (updateMany, deleteMany) use multiple id query parameters
34
+ * - CORS must expose the X-Total-Count header
35
+ *
36
+ * **Supported Operations:**
37
+ * - `getList`: GET /resource?_start=0&_end=10&_sort=id&_order=ASC
38
+ * - `getOne`: GET /resource/123
39
+ * - `getMany`: GET /resource?id=123&id=456&id=789
40
+ * - `getManyReference`: GET /resource?author_id=12&_start=0&_end=10
41
+ * - `create`: POST /resource with JSON body
42
+ * - `update`: PUT /resource/123 with JSON body
43
+ * - `updateMany`: PUT /resource?id=123&id=456 with JSON body (bulk update)
44
+ * - `delete`: DELETE /resource/123
45
+ * - `deleteMany`: DELETE /resource?id=123&id=456 (bulk delete)
46
+ *
47
+ * **Spring Boot Adaptations:**
48
+ * - Bulk operations (updateMany, deleteMany) send single requests with multiple id parameters
49
+ * - updateMany sends data fields in request body to update all specified records
50
+ * - This differs from standard ra-data-json-server which sends individual requests for bulk operations
51
+ *
52
+ * **Embedded Resources:**
53
+ * Use the `meta.embed` parameter to request related records:
54
+ * ```tsx
55
+ * useGetOne('posts', { id: 1, meta: { embed: 'author' } })
56
+ * ```
57
+ */
58
+ export default (
59
+ apiUrl: string,
60
+ httpClient = fetchUtils.fetchJson,
61
+ ): DataProvider => ({
62
+ getList: async (resource, params) => {
63
+ const { page, perPage } = params.pagination || {};
64
+ const { field, order } = params.sort || {};
65
+ const query = {
66
+ ...fetchUtils.flattenObject(params.filter),
67
+ _sort: field,
68
+ _order: order,
69
+ _start:
70
+ page != null && perPage != null ? (page - 1) * perPage : undefined,
71
+ _end: page != null && perPage != null ? page * perPage : undefined,
72
+ _embed: params?.meta?.embed,
73
+ };
74
+ const url = `${apiUrl}/${resource}?${queryString.stringify(query)}`;
75
+
76
+ const { headers, json } = await httpClient(url, {
77
+ signal: params?.signal,
78
+ });
79
+ if (!headers.has("x-total-count")) {
80
+ throw new Error(
81
+ "The X-Total-Count header is missing in the HTTP Response. The jsonServer Data Provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?",
82
+ );
83
+ }
84
+ const totalString = headers.get("x-total-count")!.split("/").pop();
85
+ if (totalString == null) {
86
+ throw new Error(
87
+ "The X-Total-Count header is invalid in the HTTP Response.",
88
+ );
89
+ }
90
+ return { data: json, total: parseInt(totalString, 10) };
91
+ },
92
+
93
+ getOne: async (resource, params) => {
94
+ let url = `${apiUrl}/${resource}/${params.id}`;
95
+ if (params?.meta?.embed) {
96
+ url += `?_embed=${params.meta.embed}`;
97
+ }
98
+ const { json } = await httpClient(url, { signal: params?.signal });
99
+ return { data: json };
100
+ },
101
+
102
+ getMany: async (resource, params) => {
103
+ const query = {
104
+ id: params.ids,
105
+ _embed: params?.meta?.embed,
106
+ };
107
+ const url = `${apiUrl}/${resource}?${queryString.stringify(query)}`;
108
+ const { json } = await httpClient(url, { signal: params?.signal });
109
+ return { data: json };
110
+ },
111
+
112
+ getManyReference: async (resource, params) => {
113
+ const { page, perPage } = params.pagination;
114
+ const { field, order } = params.sort;
115
+ const query = {
116
+ ...fetchUtils.flattenObject(params.filter),
117
+ [params.target]: params.id,
118
+ _sort: field,
119
+ _order: order,
120
+ _start: (page - 1) * perPage,
121
+ _end: page * perPage,
122
+ _embed: params?.meta?.embed,
123
+ };
124
+ const url = `${apiUrl}/${resource}?${queryString.stringify(query)}`;
125
+
126
+ const { headers, json } = await httpClient(url, {
127
+ signal: params?.signal,
128
+ });
129
+
130
+ if (!headers.has("x-total-count")) {
131
+ throw new Error(
132
+ "The X-Total-Count header is missing in the HTTP Response. The jsonServer Data Provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare X-Total-Count in the Access-Control-Expose-Headers header?",
133
+ );
134
+ }
135
+ const totalString = headers.get("x-total-count")!.split("/").pop();
136
+ if (totalString == null) {
137
+ throw new Error(
138
+ "The X-Total-Count header is invalid in the HTTP Response.",
139
+ );
140
+ }
141
+ return { data: json, total: parseInt(totalString, 10) };
142
+ },
143
+
144
+ update: async (resource, params) => {
145
+ const { json } = await httpClient(`${apiUrl}/${resource}/${params.id}`, {
146
+ method: "PUT",
147
+ body: JSON.stringify(params.data),
148
+ });
149
+ return { data: json };
150
+ },
151
+
152
+ // Spring Boot bulk update: PUT /resource?id=1&id=2&id=3 with data in body
153
+ updateMany: async (resource, params) => {
154
+ const query = {
155
+ id: params.ids,
156
+ };
157
+ const url = `${apiUrl}/${resource}?${queryString.stringify(query)}`;
158
+ const { json } = await httpClient(url, {
159
+ method: "PUT",
160
+ body: JSON.stringify(params.data),
161
+ });
162
+ return { data: json };
163
+ },
164
+
165
+ create: async (resource, params) => {
166
+ const { json } = await httpClient(`${apiUrl}/${resource}`, {
167
+ method: "POST",
168
+ body: JSON.stringify(params.data),
169
+ });
170
+ return { data: { ...params.data, ...json } as any };
171
+ },
172
+
173
+ delete: async (resource, params) => {
174
+ const { json } = await httpClient(`${apiUrl}/${resource}/${params.id}`, {
175
+ method: "DELETE",
176
+ });
177
+ return { data: json };
178
+ },
179
+
180
+ // Spring Boot bulk delete: DELETE /resource?id=1&id=2&id=3
181
+ deleteMany: async (resource, params) => {
182
+ const query = {
183
+ id: params.ids,
184
+ };
185
+ const url = `${apiUrl}/${resource}?${queryString.stringify(query)}`;
186
+ const { json } = await httpClient(url, {
187
+ method: "DELETE",
188
+ });
189
+ return { data: json };
190
+ },
191
+ });