@chill-sharp/react-client 1.1.12 → 1.1.15
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 +442 -442
- package/package.json +49 -49
- package/src/context.tsx +58 -58
- package/src/hooks.ts +515 -515
- package/src/index.ts +86 -86
- package/src/version.ts +20 -20
package/README.md
CHANGED
|
@@ -1,443 +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
|
-
|
|
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
443
|
For the complete tree model, delete behavior, and `MenuHierarchy` filtering behavior, see [../../doc/MenuGuide/README.md](../../doc/MenuGuide/README.md).
|