@chill-sharp/react-client 1.1.12

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,443 @@
1
+ # @chill-sharp/react-client
2
+
3
+ React helpers for a generic ChillSharp service.
4
+
5
+ This package wraps [`@chill-sharp/ts-client`](../chill-sharp-ts-client) and adds:
6
+
7
+ - a `ChillSharpProvider`
8
+ - `useChillSharpClient()` to access the raw client
9
+ - `useSchema()` for localized schema loading
10
+ - `useSchemaList()` for registered type discovery
11
+ - `useText()` and `useTexts()` for i18n label lookups
12
+ - `useTest()` for endpoint health checks
13
+ - `useQueryMutation()`, `useLookupMutation()`, `useEntityMutation()`, `useAutocompleteMutation()`, and `useValidateMutation()` for generic API actions
14
+
15
+ It stays generic on purpose. Payloads are plain objects so the same package can work against arbitrary ChillSharp models.
16
+
17
+ ## Install
18
+
19
+ From the repository root:
20
+
21
+ ```bash
22
+ cd extra/chill-sharp-react-client
23
+ npm install
24
+ ```
25
+
26
+ This package expects:
27
+
28
+ - `react` 18 or 19
29
+ - `@chill-sharp/ts-client`
30
+ - a runtime `fetch` implementation, which modern browsers and Node.js 18+ already provide
31
+
32
+ ## Local Linking
33
+
34
+ The package builds automatically on `npm install`, `npm pack`, and `npm link`.
35
+ Link `@chill-sharp/ts-client` first, then this package:
36
+
37
+ ```bash
38
+ cd extra/chill-sharp-ts-client
39
+ npm install
40
+ npm link
41
+
42
+ cd ../chill-sharp-react-client
43
+ npm install
44
+ npm link
45
+
46
+ cd path/to/your-react-app
47
+ npm link @chill-sharp/ts-client
48
+ npm link @chill-sharp/react-client
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```tsx
54
+ import { ChillSharpProvider, useSchema, useSchemaList, useText, useTexts } from "@chill-sharp/react-client";
55
+
56
+ function BlogSchemaName() {
57
+ const { data, isLoading, error } = useSchema("Model.Blog", "default");
58
+
59
+ if (isLoading) return <p>Loading...</p>;
60
+ if (error) return <p>Failed to load schema.</p>;
61
+
62
+ return <h1>{String(data?.DisplayName ?? "")}</h1>;
63
+ }
64
+
65
+ export function App() {
66
+ return (
67
+ <ChillSharpProvider
68
+ baseUrl="http://localhost:5000/api/chill"
69
+ options={{ cultureName: "it-IT" }}
70
+ >
71
+ <BlogSchemaName />
72
+ </ChillSharpProvider>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ## Provider Setup
78
+
79
+ ### Provider creates the client
80
+
81
+ ```tsx
82
+ <ChillSharpProvider
83
+ baseUrl="http://localhost:5000/api/chill"
84
+ options={{
85
+ cultureName: "it-IT",
86
+ accessToken: "your-jwt-token"
87
+ }}
88
+ >
89
+ <App />
90
+ </ChillSharpProvider>
91
+ ```
92
+
93
+ ### Provider receives a prebuilt client
94
+
95
+ ```tsx
96
+ import { ChillSharpClient } from "@chill-sharp/react-client";
97
+
98
+ const client = new ChillSharpClient("http://localhost:5000/api/chill", {
99
+ cultureName: "it-IT"
100
+ });
101
+
102
+ <ChillSharpProvider baseUrl="http://localhost:5000/api/chill" client={client}>
103
+ <App />
104
+ </ChillSharpProvider>;
105
+ ```
106
+
107
+ ## Hooks
108
+
109
+ ### `useChillSharpClient()`
110
+
111
+ Use the raw client when you need the full API surface.
112
+
113
+ ```tsx
114
+ import { useEffect, useState } from "react";
115
+ import { useChillSharpClient } from "@chill-sharp/react-client";
116
+
117
+ function PostCount() {
118
+ const client = useChillSharpClient();
119
+ const [count, setCount] = useState<number>(0);
120
+
121
+ useEffect(() => {
122
+ void client.query({
123
+ ChillType: "Query.PostQuery",
124
+ ResultProperties: [{ Name: "Guid" }]
125
+ }).then(result => {
126
+ const rows = Array.isArray(result.Results) ? result.Results : [];
127
+ setCount(rows.length);
128
+ });
129
+ }, [client]);
130
+
131
+ return <span>{count}</span>;
132
+ }
133
+ ```
134
+
135
+ ### `useSchema()`
136
+
137
+ `useSchema()` loads metadata and tracks `isLoading`, `error`, and `reload`.
138
+
139
+ ```tsx
140
+ const { data, isLoading, error, reload } = useSchema("Model.Post", "default");
141
+ const handleAttachments = data?.handleAttachments;
142
+ const relations = data?.relations ?? [];
143
+ ```
144
+
145
+ You can override the provider culture for one call:
146
+
147
+ ```tsx
148
+ const englishSchema = useSchema("Model.Post", "default", "en-GB");
149
+ ```
150
+
151
+ Pass `update: true` as the fourth argument when you want the server to refresh a persisted schema from the current runtime model:
152
+
153
+ ```tsx
154
+ const refreshedSchema = useSchema("Model.Post", "default", undefined, true);
155
+ ```
156
+
157
+ Existing properties keep their saved metadata, new model properties are added, and removed model properties are dropped from the persisted schema.
158
+
159
+ The schema and entity-option payloads re-exported by this package include `handleAttachments` and schema-level `relations`. Query payloads also include `ordering`, and entity payloads include `position` with backend default `0`.
160
+
161
+ ### `useSchemaList()`
162
+
163
+ ```tsx
164
+ const { data, isLoading, error, reload } = useSchemaList();
165
+ const englishSchemaList = useSchemaList("en-GB");
166
+ ```
167
+
168
+ ### `useText()`
169
+
170
+ ```tsx
171
+ const { data, isLoading } = useText({
172
+ LabelGuid: "4e16f6c0-6b95-4d67-98bc-9f4d0d63eaf1",
173
+ CultureName: "it-IT",
174
+ PrimaryCultureName: "en-GB",
175
+ PrimaryDefaultText: "Blog title",
176
+ SecondaryCultureName: "it-IT",
177
+ SecondaryDefaultText: "Titolo del blog"
178
+ });
179
+ ```
180
+
181
+ ### `useTexts()`
182
+
183
+ ```tsx
184
+ const { data: texts, isLoading: isTextsLoading } = useTexts([
185
+ {
186
+ LabelGuid: "4e16f6c0-6b95-4d67-98bc-9f4d0d63eaf1",
187
+ CultureName: "it-IT",
188
+ PrimaryCultureName: "en-GB",
189
+ PrimaryDefaultText: "Blog title",
190
+ SecondaryCultureName: "it-IT",
191
+ SecondaryDefaultText: "Titolo del blog"
192
+ },
193
+ {
194
+ LabelGuid: "2f6ef6f7-b0a9-44f8-bfd2-a3b3ed5b9a81",
195
+ CultureName: "it-IT",
196
+ PrimaryCultureName: "en-GB",
197
+ PrimaryDefaultText: "Blog url",
198
+ SecondaryCultureName: "it-IT",
199
+ SecondaryDefaultText: "Url del blog"
200
+ }
201
+ ]);
202
+ ```
203
+
204
+ ### `useTest()`
205
+
206
+ ```tsx
207
+ const { data, isLoading, reload } = useTest();
208
+ ```
209
+
210
+ `useTest()` calls `GET /api/chill/test` and returns the plain-text service status.
211
+
212
+ ### `useQueryMutation()`
213
+
214
+ Use `useQueryMutation()` when `ChillType` points to a concrete query type such as `Query.PostQuery`.
215
+
216
+ ` sx
217
+ const { execute, data, isLoading } = useQueryMutation();
218
+
219
+ async function runQuery() {
220
+ await execute({
221
+ chillType: "Query.PostQuery",
222
+ properties: { title: "Hello" },
223
+ ordering: {
224
+ propertyName: "Position",
225
+ direction: "ASC"
226
+ },
227
+ resultProperties: [{ name: "Guid" }, { name: "Title" }]
228
+ });
229
+ }
230
+ ```
231
+
232
+ If `ordering.propertyName` points to a Chill entity reference, the backend orders by that referenced entity `Label`.
233
+
234
+ ### `useLookupMutation()`
235
+
236
+ ```tsx
237
+ const lookupPosts = useLookupMutation();
238
+
239
+ await lookupPosts.execute({
240
+ chillType: "Model.Post",
241
+ properties: {
242
+ fullTextSearch: "Ada Lovelace"
243
+ },
244
+ ordering: {
245
+ propertyName: "Blog",
246
+ direction: "ASC"
247
+ },
248
+ resultProperties: [{ name: "Guid" }, { name: "Title" }]
249
+ });
250
+ ```
251
+
252
+ Use `useLookupMutation()` when `ChillType` points to an entity type and you only need generic full-text search.
253
+
254
+ ### `useAutocompleteMutation()`
255
+
256
+ ```tsx
257
+ const autocompletePost = useAutocompleteMutation();
258
+
259
+ await autocompletePost.execute({
260
+ ChillType: "Model.Post",
261
+ Properties: {
262
+ Title: " Draft title "
263
+ }
264
+ });
265
+ ```
266
+
267
+ ### `useValidateMutation()`
268
+
269
+ ```tsx
270
+ const validatePost = useValidateMutation();
271
+
272
+ const errors = await validatePost.execute({
273
+ ChillType: "Model.Post",
274
+ Properties: {
275
+ Title: ""
276
+ }
277
+ });
278
+ ```
279
+
280
+ ### `useEntityMutation()`
281
+
282
+ Use one hook instance per entity action:
283
+
284
+ ```tsx
285
+ const createPost = useEntityMutation("create");
286
+ const updatePost = useEntityMutation("update");
287
+ const deletePost = useEntityMutation("delete");
288
+ ```
289
+
290
+ Example:
291
+
292
+ ```tsx
293
+ await createPost.execute({
294
+ chillType: "Model.Post",
295
+ guid: crypto.randomUUID(),
296
+ position: 10,
297
+ properties: {
298
+ title: "New title",
299
+ author: "Grace Hopper"
300
+ }
301
+ });
302
+ ```
303
+
304
+ ## Attachments
305
+
306
+ Use the raw client from `useChillSharpClient()` for attachment helpers:
307
+
308
+ ```tsx
309
+ function AttachmentActions({ postGuid }: { postGuid: string }) {
310
+ const client = useChillSharpClient();
311
+
312
+ async function upload() {
313
+ await client.uploadAttachment(
314
+ { ChillType: "Model.Post", Guid: postGuid },
315
+ {
316
+ fileName: "contract.txt",
317
+ content: new Blob(["hello attachment"], { type: "text/plain" }),
318
+ contentType: "text/plain"
319
+ },
320
+ {
321
+ title: "Contract",
322
+ description: "Signed draft",
323
+ isPublic: false
324
+ }
325
+ );
326
+ }
327
+
328
+ return <button onClick={() => void upload()}>Upload</button>;
329
+ }
330
+ ```
331
+
332
+ ## Chunk batches
333
+
334
+ Call `chunk()` through `useChillSharpClient()` when several operations should be sent in one request.
335
+
336
+ ```tsx
337
+ function SaveBatch() {
338
+ const client = useChillSharpClient();
339
+
340
+ async function executeBatch(existingGuid: string) {
341
+ await client.chunk([
342
+ { Index: 0, Verb: "transaction" },
343
+ {
344
+ Index: 1,
345
+ Verb: "create",
346
+ Entity: {
347
+ ChillType: "Model.Post",
348
+ Guid: crypto.randomUUID(),
349
+ Properties: {
350
+ Title: "Batched post",
351
+ Author: "Grace Hopper"
352
+ }
353
+ }
354
+ },
355
+ {
356
+ Index: 2,
357
+ Verb: "update",
358
+ Entity: {
359
+ ChillType: "Model.Post",
360
+ Guid: existingGuid,
361
+ Properties: {
362
+ Title: "Updated in the same batch"
363
+ }
364
+ }
365
+ },
366
+ { Index: 3, Verb: "commit" }
367
+ ]);
368
+ }
369
+
370
+ return null;
371
+ }
372
+ ```
373
+
374
+ Use `transaction` and `commit` only when the enclosed write operations must be committed together.
375
+
376
+ ## Authentication
377
+
378
+ Because the React package reuses the TypeScript client, it inherits the same auth behavior:
379
+
380
+ - pass `accessToken` when you already have a token
381
+ - pass `username` and `password` when the client should log in and refresh automatically
382
+ - pass `DisplayCultureName` during registration when the server should preset auth-user display preferences
383
+ - call `useChillSharpClient()` when you need direct access to auth account methods, auth management methods, or schema-management methods like `getEntityOptions()`, `setEntityOptions()`, `getMenu()`, `setMenu()`, and `deleteMenu()`
384
+ - schema and entity option payloads re-exported by this package include the `handleAttachments` flag from `@chill-sharp/ts-client`
385
+ - query payloads re-exported by this package include `ordering`, and entity payloads include `position`
386
+
387
+ Auth user list/detail payloads exposed through the raw client include:
388
+
389
+ - `displayCultureName`
390
+ - `displayTimeZone`
391
+ - `displayDateFormat`
392
+ - `displayNumberFormat`
393
+
394
+ ## Error Handling
395
+
396
+ The hooks expose the last thrown error. The underlying client throws `ChillSharpClientError`.
397
+
398
+ ```tsx
399
+ import { ChillSharpClientError, useSchema } from "@chill-sharp/react-client";
400
+
401
+ function SchemaStatus() {
402
+ const { error } = useSchema("Model.Post", "default");
403
+
404
+ if (error instanceof ChillSharpClientError) {
405
+ return <pre>{error.responseText}</pre>;
406
+ }
407
+
408
+ return null;
409
+ }
410
+ ```
411
+
412
+ ## When To Use The React Package
413
+
414
+ Use this package when you want React-friendly state handling on top of the generic client.
415
+
416
+ Use the plain TypeScript package instead when:
417
+
418
+ - you are not using React
419
+ - you already have your own data-fetching layer
420
+ - you want complete control over caching, retries, and optimistic updates
421
+
422
+ ## Generic Payload Strategy
423
+
424
+ This package does not generate React components or model-specific hooks for your Chill entities.
425
+
426
+ That is intentional:
427
+
428
+ - ChillSharp models are application-specific
429
+ - generic object payloads are enough to talk to the standard ChillSharp API
430
+ - model-specific React hooks are better generated from OpenAPI for each host application
431
+
432
+ If you need typed model clients, generate them from your host OpenAPI document as described in [doc/ClientGeneration/README.md](../../doc/ClientGeneration/README.md).
433
+
434
+
435
+
436
+
437
+
438
+
439
+ ## Menu endpoints
440
+
441
+ When you use the raw client from `useChillSharpClient()`, `getMenu()` loads root menu nodes or the direct children of one menu item, `setMenu()` creates or updates one menu item, and `deleteMenu()` removes one menu item together with its child subtree. Menu items include `positionNo`, which the backend persists and uses to order siblings.
442
+
443
+ For the complete tree model, delete behavior, and `MenuHierarchy` filtering behavior, see [../../doc/MenuGuide/README.md](../../doc/MenuGuide/README.md).
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { ChillSharpClient } from "@chill-sharp/ts-client";
3
+ import type { ChillSharpClientOptions } from "@chill-sharp/ts-client";
4
+ export interface ChillSharpProviderProps {
5
+ baseUrl: string;
6
+ client?: ChillSharpClient;
7
+ options?: ChillSharpClientOptions;
8
+ children: React.ReactNode;
9
+ }
10
+ export declare function ChillSharpProvider(props: ChillSharpProviderProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function useChillSharpClient(): ChillSharpClient;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * ChillSharp is a lightweight .NET library that sits on top of Entity Framework Core
4
+ * and turns an existing data model into a fully working REST API with almost no setup.
5
+ * Copyright (C) 2025 Andrea Piovesan
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU Affero General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU Affero General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License
18
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ */
20
+ import { createContext, useContext, useRef } from "react";
21
+ import { ChillSharpClient } from "@chill-sharp/ts-client";
22
+ const ChillSharpContext = createContext(null);
23
+ export function ChillSharpProvider(props) {
24
+ const clientRef = useRef(props.client ?? null);
25
+ if (props.client && clientRef.current !== props.client) {
26
+ clientRef.current = props.client;
27
+ }
28
+ if (!clientRef.current) {
29
+ clientRef.current = new ChillSharpClient(props.baseUrl, props.options);
30
+ }
31
+ return (_jsx(ChillSharpContext.Provider, { value: clientRef.current, children: props.children }));
32
+ }
33
+ export function useChillSharpClient() {
34
+ const client = useContext(ChillSharpContext);
35
+ if (!client) {
36
+ throw new Error("useChillSharpClient must be used inside ChillSharpProvider.");
37
+ }
38
+ return client;
39
+ }
@@ -0,0 +1,31 @@
1
+ import type { ChillDtoSchema, ChillDtoSchemaListItem, ChillUserPreferences, ChillEntityChangeCallback, ChillValidationError, GetTextRequest, GetTextResponse, JsonObject } from "@chill-sharp/ts-client";
2
+ export interface UseChillAsyncState<TData> {
3
+ data: TData | null;
4
+ error: unknown;
5
+ isLoading: boolean;
6
+ reload: () => Promise<TData | null>;
7
+ }
8
+ export interface UseChillMutationState<TData> {
9
+ data: TData | null;
10
+ error: unknown;
11
+ isLoading: boolean;
12
+ execute: (...args: unknown[]) => Promise<TData>;
13
+ reset: () => void;
14
+ }
15
+ export interface UseChillSubscriptionState {
16
+ error: unknown;
17
+ isSubscribed: boolean;
18
+ }
19
+ export declare function useSchema(chillType: string, chillViewCode?: string, cultureName?: string, update?: boolean): UseChillAsyncState<ChillDtoSchema>;
20
+ export declare function useSchemaList(cultureName?: string): UseChillAsyncState<ChillDtoSchemaListItem[]>;
21
+ export declare function useText(request: GetTextRequest): UseChillAsyncState<GetTextResponse>;
22
+ export declare function useTexts(requests: GetTextRequest[]): UseChillAsyncState<Array<GetTextResponse | null>>;
23
+ export declare function useVersion(): string;
24
+ export declare function useTest(): UseChillAsyncState<string>;
25
+ export declare function useCurrentUserPreferences(): UseChillAsyncState<ChillUserPreferences>;
26
+ export declare function useQueryMutation(): UseChillMutationState<JsonObject>;
27
+ export declare function useLookupMutation(): UseChillMutationState<JsonObject>;
28
+ export declare function useAutocompleteMutation(): UseChillMutationState<JsonObject>;
29
+ export declare function useValidateMutation(): UseChillMutationState<ChillValidationError[]>;
30
+ export declare function useEntityMutation(action: "find" | "create" | "update" | "delete"): UseChillMutationState<JsonObject | null>;
31
+ export declare function useEntityChanges(chillType: string, onChanges: ChillEntityChangeCallback, guid?: string | null): UseChillSubscriptionState;