@intrig/plugin-react 0.0.1 → 0.0.2-2
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/dist/index.cjs +4260 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4235 -0
- package/package.json +6 -3
- package/.swcrc +0 -29
- package/README.md +0 -7
- package/eslint.config.mjs +0 -19
- package/project.json +0 -29
- package/rollup.config.cjs +0 -54
- package/rollup.config.mjs +0 -33
- package/src/index.ts +0 -2
- package/src/lib/code-generator.ts +0 -79
- package/src/lib/get-endpoint-documentation.ts +0 -35
- package/src/lib/get-schema-documentation.ts +0 -11
- package/src/lib/internal-types.ts +0 -15
- package/src/lib/plugin-react.ts +0 -22
- package/src/lib/templates/context.template.ts +0 -74
- package/src/lib/templates/docs/__snapshots__/async-hook.spec.ts.snap +0 -889
- package/src/lib/templates/docs/__snapshots__/download-hook.spec.ts.snap +0 -1445
- package/src/lib/templates/docs/__snapshots__/react-hook.spec.ts.snap +0 -1371
- package/src/lib/templates/docs/__snapshots__/sse-hook.spec.ts.snap +0 -2008
- package/src/lib/templates/docs/async-hook.spec.ts +0 -92
- package/src/lib/templates/docs/async-hook.ts +0 -226
- package/src/lib/templates/docs/download-hook.spec.ts +0 -182
- package/src/lib/templates/docs/download-hook.ts +0 -170
- package/src/lib/templates/docs/react-hook.spec.ts +0 -97
- package/src/lib/templates/docs/react-hook.ts +0 -323
- package/src/lib/templates/docs/schema.ts +0 -105
- package/src/lib/templates/docs/sse-hook.spec.ts +0 -207
- package/src/lib/templates/docs/sse-hook.ts +0 -221
- package/src/lib/templates/extra.template.ts +0 -198
- package/src/lib/templates/index.template.ts +0 -14
- package/src/lib/templates/intrigMiddleware.template.ts +0 -21
- package/src/lib/templates/logger.template.ts +0 -67
- package/src/lib/templates/media-type-utils.template.ts +0 -191
- package/src/lib/templates/network-state.template.ts +0 -702
- package/src/lib/templates/packageJson.template.ts +0 -63
- package/src/lib/templates/provider/__tests__/provider-templates.spec.ts +0 -209
- package/src/lib/templates/provider/axios-config.template.ts +0 -49
- package/src/lib/templates/provider/hooks.template.ts +0 -240
- package/src/lib/templates/provider/interfaces.template.ts +0 -72
- package/src/lib/templates/provider/intrig-provider-stub.template.ts +0 -73
- package/src/lib/templates/provider/intrig-provider.template.ts +0 -185
- package/src/lib/templates/provider/main.template.ts +0 -48
- package/src/lib/templates/provider/reducer.template.ts +0 -50
- package/src/lib/templates/provider/status-trap.template.ts +0 -80
- package/src/lib/templates/provider.template.ts +0 -698
- package/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts +0 -196
- package/src/lib/templates/source/controller/method/clientIndex.template.ts +0 -38
- package/src/lib/templates/source/controller/method/download.template.ts +0 -256
- package/src/lib/templates/source/controller/method/params.template.ts +0 -31
- package/src/lib/templates/source/controller/method/requestHook.template.ts +0 -220
- package/src/lib/templates/source/type/typeTemplate.ts +0 -257
- package/src/lib/templates/swcrc.template.ts +0 -25
- package/src/lib/templates/tsconfig.template.ts +0 -37
- package/src/lib/templates/type-utils.template.ts +0 -28
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -20
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { reactHookDocs } from './react-hook';
|
|
2
|
-
|
|
3
|
-
// The descriptor type is imported in the implementation from "@intrig/plugin-sdk".
|
|
4
|
-
// For the test we can just use the plain object shape to avoid tight coupling.
|
|
5
|
-
|
|
6
|
-
describe('reactHookDocs', () => {
|
|
7
|
-
it('generates markdown that matches snapshot for a simple REST descriptor (no body, no path params)', async () => {
|
|
8
|
-
const descriptor = {
|
|
9
|
-
id: '1',
|
|
10
|
-
name: 'getUser',
|
|
11
|
-
type: 'rest' as const,
|
|
12
|
-
source: 'demo_api',
|
|
13
|
-
path: 'users',
|
|
14
|
-
data: {
|
|
15
|
-
method: 'GET',
|
|
16
|
-
paths: ['/api/users'],
|
|
17
|
-
operationId: 'getUser',
|
|
18
|
-
// no requestBody
|
|
19
|
-
// no variables
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const result = await reactHookDocs(descriptor as any);
|
|
24
|
-
|
|
25
|
-
expect(result.path).toBe('react-hook.md');
|
|
26
|
-
|
|
27
|
-
const md = result.content;
|
|
28
|
-
|
|
29
|
-
expect(md).toMatchSnapshot();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('snapshot — request body only (no path params)', async () => {
|
|
33
|
-
const descriptor = {
|
|
34
|
-
id: '2',
|
|
35
|
-
name: 'createUser',
|
|
36
|
-
type: 'rest' as const,
|
|
37
|
-
source: 'demo_api',
|
|
38
|
-
path: 'users',
|
|
39
|
-
data: {
|
|
40
|
-
method: 'POST',
|
|
41
|
-
paths: ['/api/users'],
|
|
42
|
-
operationId: 'createUser',
|
|
43
|
-
requestBody: 'CreateUserRequest',
|
|
44
|
-
// no variables
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const result = await reactHookDocs(descriptor as any);
|
|
49
|
-
expect(result.path).toBe('react-hook.md');
|
|
50
|
-
expect(result.content).toMatchSnapshot();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('snapshot — path params only (no request body)', async () => {
|
|
54
|
-
const descriptor = {
|
|
55
|
-
id: '3',
|
|
56
|
-
name: 'getUserById',
|
|
57
|
-
type: 'rest' as const,
|
|
58
|
-
source: 'demo_api',
|
|
59
|
-
path: 'users/{id}',
|
|
60
|
-
data: {
|
|
61
|
-
method: 'GET',
|
|
62
|
-
paths: ['/api/users/{id}'],
|
|
63
|
-
operationId: 'getUserById',
|
|
64
|
-
variables: [
|
|
65
|
-
{ name: 'id', in: 'path', ref: '#/components/schemas/Id' },
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const result = await reactHookDocs(descriptor as any);
|
|
71
|
-
expect(result.path).toBe('react-hook.md');
|
|
72
|
-
expect(result.content).toMatchSnapshot();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('snapshot — request body and path params', async () => {
|
|
76
|
-
const descriptor = {
|
|
77
|
-
id: '4',
|
|
78
|
-
name: 'updateUser',
|
|
79
|
-
type: 'rest' as const,
|
|
80
|
-
source: 'demo_api',
|
|
81
|
-
path: 'users/{id}',
|
|
82
|
-
data: {
|
|
83
|
-
method: 'PUT',
|
|
84
|
-
paths: ['/api/users/{id}'],
|
|
85
|
-
operationId: 'updateUser',
|
|
86
|
-
requestBody: 'UpdateUserRequest',
|
|
87
|
-
variables: [
|
|
88
|
-
{ name: 'id', in: 'PATH', ref: '#/components/schemas/Id' },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const result = await reactHookDocs(descriptor as any);
|
|
94
|
-
expect(result.path).toBe('react-hook.md');
|
|
95
|
-
expect(result.content).toMatchSnapshot();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import { camelCase, mdLiteral, pascalCase, ResourceDescriptor, RestData } from "@intrig/plugin-sdk"
|
|
2
|
-
|
|
3
|
-
export function reactHookDocs(descriptor: ResourceDescriptor<RestData>) {
|
|
4
|
-
const md = mdLiteral('react-hook.md')
|
|
5
|
-
|
|
6
|
-
// ===== Derived names (preserve these) =====
|
|
7
|
-
const hasPathParams = (descriptor.data.variables ?? []).some(
|
|
8
|
-
(v: any) => v.in?.toUpperCase() === 'PATH',
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
const actionName = camelCase(descriptor.name) // e.g. getUser
|
|
12
|
-
const respVar = `${actionName}Resp` // e.g. getUserResp
|
|
13
|
-
const dataVar = `${actionName}Data` // e.g. getUserData
|
|
14
|
-
const clearName = `clear${pascalCase(descriptor.name)}` // e.g. clearGetUser
|
|
15
|
-
|
|
16
|
-
const requestBodyVar = descriptor.data.requestBody
|
|
17
|
-
? camelCase(descriptor.data.requestBody)
|
|
18
|
-
: undefined
|
|
19
|
-
const requestBodyType = descriptor.data.requestBody
|
|
20
|
-
? pascalCase(descriptor.data.requestBody)
|
|
21
|
-
: undefined
|
|
22
|
-
|
|
23
|
-
const paramsVar = hasPathParams ? `${actionName}Params` : undefined // e.g. getUserParams
|
|
24
|
-
const paramsType = hasPathParams ? `${pascalCase(descriptor.name)}Params` : undefined // e.g. GetUserParams
|
|
25
|
-
|
|
26
|
-
const responseTypeName = `${pascalCase(descriptor.name)}ResponseBody`
|
|
27
|
-
|
|
28
|
-
return md`
|
|
29
|
-
# Intrig React Hooks — Quick Guide
|
|
30
|
-
|
|
31
|
-
## Copy-paste starter (fast lane)
|
|
32
|
-
|
|
33
|
-
### 1) Hook import
|
|
34
|
-
${"```ts"}
|
|
35
|
-
import { use${pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
|
|
36
|
-
${"```"}
|
|
37
|
-
|
|
38
|
-
### 2) Utility guards
|
|
39
|
-
${"```ts"}
|
|
40
|
-
import { isPending, isError, isSuccess } from '@intrig/react';
|
|
41
|
-
${"```"}
|
|
42
|
-
|
|
43
|
-
### 3) Hook instance (auto-clear on unmount)
|
|
44
|
-
${"```ts"}
|
|
45
|
-
const [${respVar}, ${actionName}] = use${pascalCase(descriptor.name)}({ clearOnUnmount: true });
|
|
46
|
-
${"```"}
|
|
47
|
-
|
|
48
|
-
Intrig stateful hooks expose a **NetworkState** plus **actions** to fetch / clear.
|
|
49
|
-
Use this when you want a cached, reusable result tied to a global store.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## TL;DR (copy–paste)
|
|
54
|
-
${"```tsx"}
|
|
55
|
-
import { use${pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
|
|
56
|
-
import { isPending, isError, isSuccess } from '@intrig/react';
|
|
57
|
-
import { useEffect, useMemo } from 'react';
|
|
58
|
-
|
|
59
|
-
export default function Example() {
|
|
60
|
-
const [${respVar}, ${actionName}] = use${pascalCase(descriptor.name)}({ clearOnUnmount: true });
|
|
61
|
-
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
${actionName}(${[requestBodyVar, paramsVar ?? '{}'].filter(Boolean).join(', ')});
|
|
64
|
-
}, [${[''+actionName, requestBodyVar, paramsVar].filter(Boolean).join(', ')}]);
|
|
65
|
-
|
|
66
|
-
const ${dataVar} = useMemo(
|
|
67
|
-
() => (isSuccess(${respVar}) ? ${respVar}.data : undefined),
|
|
68
|
-
[${respVar}]
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (isPending(${respVar})) return <>Loading…</>;
|
|
72
|
-
if (isError(${respVar})) return <>Error: {String(${respVar}.error)}</>;
|
|
73
|
-
return <pre>{JSON.stringify(${dataVar}, null, 2)}</pre>;
|
|
74
|
-
}
|
|
75
|
-
${"```"}
|
|
76
|
-
|
|
77
|
-
${requestBodyType || paramsType ? `### Optional types (if generated by your build)
|
|
78
|
-
${"```ts"}
|
|
79
|
-
${requestBodyType ? `import type { ${requestBodyType} } from '@intrig/react/${descriptor.source}/components/schemas/${requestBodyType}';
|
|
80
|
-
` : ''}${paramsType ? `import type { ${paramsType} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.params';
|
|
81
|
-
` : ''}// Prefer the concrete response type:
|
|
82
|
-
import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.response';
|
|
83
|
-
${"```"}
|
|
84
|
-
` : ''}
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## Hook API
|
|
89
|
-
${"```ts"}
|
|
90
|
-
// Options are consistent across hooks.
|
|
91
|
-
type UseHookOptions = {
|
|
92
|
-
/** Execute once after mount with provided params/body (if required). */
|
|
93
|
-
fetchOnMount?: boolean;
|
|
94
|
-
/** Reset the state on unmount (recommended). */
|
|
95
|
-
clearOnUnmount?: boolean;
|
|
96
|
-
/** Distinguish multiple instances of the same hook. */
|
|
97
|
-
key?: string;
|
|
98
|
-
/** Initial path params for endpoints that require them. */
|
|
99
|
-
params?: ${paramsType ?? 'unknown'};
|
|
100
|
-
/** Initial request body (for POST/PUT/etc.). */
|
|
101
|
-
body?: ${requestBodyType ?? 'unknown'};
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Prefer concrete types if your build emits them:
|
|
105
|
-
// import type { ${responseTypeName} } from '@intrig/react/${descriptor.path}/${pascalCase(descriptor.name)}.response';
|
|
106
|
-
|
|
107
|
-
type ${pascalCase(descriptor.name)}Data = ${'typeof '+responseTypeName !== 'undefined' ? responseTypeName : 'unknown'}; // replace with ${responseTypeName} if generated
|
|
108
|
-
type ${pascalCase(descriptor.name)}Request = { params?: ${paramsType ?? 'unknown'}; body?: ${requestBodyType ?? 'unknown'}; };
|
|
109
|
-
|
|
110
|
-
// Signature (shape shown; concrete generics vary per generated hook)
|
|
111
|
-
declare function use${pascalCase(descriptor.name)}(options?: UseHookOptions): [
|
|
112
|
-
NetworkState<${pascalCase(descriptor.name)}Data>,
|
|
113
|
-
(req: ${pascalCase(descriptor.name)}Request) => void,
|
|
114
|
-
() => void
|
|
115
|
-
];
|
|
116
|
-
${"```"}
|
|
117
|
-
|
|
118
|
-
### NetworkState & guards
|
|
119
|
-
${"```ts"}
|
|
120
|
-
import { isPending, isError, isSuccess } from '@intrig/react';
|
|
121
|
-
${"```"}
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Conceptual model (Stateful = single source of truth)
|
|
126
|
-
- Lives in a shared store keyed by \`key\` + request signature.
|
|
127
|
-
- Best for **read** endpoints you want to **reuse** or keep **warm** (lists, details, search).
|
|
128
|
-
- Lifecycle helpers:
|
|
129
|
-
- \`fetchOnMount\` to kick off automatically.
|
|
130
|
-
- \`clearOnUnmount\` to clean up (recommended default).
|
|
131
|
-
- \`key\` to isolate multiple independent instances.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Usage patterns
|
|
136
|
-
|
|
137
|
-
### 1) Controlled (most explicit)
|
|
138
|
-
${"```tsx"}
|
|
139
|
-
const [${respVar}, ${actionName}, ${clearName}] = use${pascalCase(descriptor.name)}();
|
|
140
|
-
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
${actionName}(${[requestBodyVar, paramsVar ?? '{}'].filter(Boolean).join(', ')});
|
|
143
|
-
return ${clearName}; // optional cleanup
|
|
144
|
-
}, [${[''+actionName, clearName].join(', ')}]);
|
|
145
|
-
${"```"}
|
|
146
|
-
|
|
147
|
-
<details><summary>Description</summary>
|
|
148
|
-
<p><strong>Use when</strong> you need explicit control over when a request fires, what params/body it uses, and when to clean up. Ideal for search forms, pagination, conditional fetches.</p>
|
|
149
|
-
</details>
|
|
150
|
-
|
|
151
|
-
### 2) Lifecycle-bound (shorthand)
|
|
152
|
-
${"```tsx"}
|
|
153
|
-
const [${respVar}] = use${pascalCase(descriptor.name)}({
|
|
154
|
-
fetchOnMount: true,
|
|
155
|
-
clearOnUnmount: true,
|
|
156
|
-
${requestBodyType ? `body: ${requestBodyVar},` : ''} ${paramsType ? `params: ${paramsVar ?? '{}'},` : 'params: {},'}
|
|
157
|
-
});
|
|
158
|
-
${"```"}
|
|
159
|
-
|
|
160
|
-
<details><summary>Description</summary>
|
|
161
|
-
<p><strong>Use when</strong> the data should follow the component lifecycle: fetch once on mount, reset on unmount.</p>
|
|
162
|
-
</details>
|
|
163
|
-
|
|
164
|
-
### 3) Passive observer (render when data arrives)
|
|
165
|
-
${"```tsx"}
|
|
166
|
-
const [${respVar}] = use${pascalCase(descriptor.name)}();
|
|
167
|
-
return isSuccess(${respVar}) ? <>{String(${respVar}.data)}</> : null;
|
|
168
|
-
${"```"}
|
|
169
|
-
|
|
170
|
-
<details><summary>Description</summary>
|
|
171
|
-
<p><strong>Use when</strong> another part of the app triggers the fetch and this component only reads the state.</p>
|
|
172
|
-
</details>
|
|
173
|
-
|
|
174
|
-
### 4) Keep previous success while refetching (sticky)
|
|
175
|
-
${"```tsx"}
|
|
176
|
-
const [${dataVar}, set${pascalCase(descriptor.name)}Data] = useState<any>();
|
|
177
|
-
const [${respVar}, ${actionName}] = use${pascalCase(descriptor.name)}();
|
|
178
|
-
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
if (isSuccess(${respVar})) set${pascalCase(descriptor.name)}Data(${respVar}.data);
|
|
181
|
-
}, [${respVar}]);
|
|
182
|
-
|
|
183
|
-
return (
|
|
184
|
-
<>
|
|
185
|
-
{isPending(${respVar}) && ${dataVar} ? <div>Refreshing…</div> : null}
|
|
186
|
-
<pre>{JSON.stringify(isSuccess(${respVar}) ? ${respVar}.data : ${dataVar}, null, 2)}</pre>
|
|
187
|
-
</>
|
|
188
|
-
);
|
|
189
|
-
${"```"}
|
|
190
|
-
|
|
191
|
-
<details><summary>Description</summary>
|
|
192
|
-
<p><strong>Use when</strong> you want SWR-like UX without flicker.</p>
|
|
193
|
-
</details>
|
|
194
|
-
|
|
195
|
-
### 5) Multiple instances of the same hook (isolate with \`key\`)
|
|
196
|
-
${"```tsx"}
|
|
197
|
-
const a = use${pascalCase(descriptor.name)}({ key: 'A', fetchOnMount: true, ${paramsType ? `params: ${paramsVar ?? '{}'} ` : 'params: {}'} });
|
|
198
|
-
const b = use${pascalCase(descriptor.name)}({ key: 'B', fetchOnMount: true, ${paramsType ? `params: ${paramsVar ?? '{}'} ` : 'params: {}'} });
|
|
199
|
-
${"```"}
|
|
200
|
-
|
|
201
|
-
<details><summary>Description</summary>
|
|
202
|
-
<p>Use unique keys to prevent state collisions.</p>
|
|
203
|
-
</details>
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Before / After mini-migrations
|
|
208
|
-
|
|
209
|
-
### If you mistakenly used Stateful for a simple submit → switch to Async
|
|
210
|
-
${"```diff"}
|
|
211
|
-
- const [${respVar}, ${actionName}] = use${pascalCase(descriptor.name)}();
|
|
212
|
-
- ${actionName}(${[requestBodyVar, paramsVar ?? '{}'].filter(Boolean).join(', ')});
|
|
213
|
-
+ const [fn] = use${pascalCase(descriptor.name)}Async();
|
|
214
|
-
+ await fn(${[requestBodyVar, paramsVar].filter(Boolean).join(', ')});
|
|
215
|
-
${"```"}
|
|
216
|
-
|
|
217
|
-
### If you started with Async but need to read later in another component → Stateful
|
|
218
|
-
${"```diff"}
|
|
219
|
-
- const [fn] = use${pascalCase(descriptor.name)}Async();
|
|
220
|
-
- const data = await fn(${[requestBodyVar, paramsVar].filter(Boolean).join(', ')});
|
|
221
|
-
+ const [${respVar}, ${actionName}] = use${pascalCase(descriptor.name)}({ fetchOnMount: true, clearOnUnmount: true, ${paramsType ? `params: ${paramsVar ?? '{}'},` : 'params: {},'} ${requestBodyType ? `body: ${requestBodyVar}` : ''} });
|
|
222
|
-
+ // read from ${respVar} anywhere with use${pascalCase(descriptor.name)}()
|
|
223
|
-
${"```"}
|
|
224
|
-
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
## Anti-patterns
|
|
228
|
-
<details><summary>Don’t use Stateful for field validations or a one-off submit</summary>
|
|
229
|
-
Use the Async variant instead: \`const [fn] = use${pascalCase(descriptor.name)}Async()\`.
|
|
230
|
-
</details>
|
|
231
|
-
<details><summary>Don’t use Async for long-lived lists or detail views</summary>
|
|
232
|
-
Use Stateful so other components can read the same data and you can avoid refetch churn.
|
|
233
|
-
</details>
|
|
234
|
-
<details><summary>Don’t forget required \`params\` when using \`fetchOnMount\`</summary>
|
|
235
|
-
Provide \`params\` (and \`body\` if applicable) or switch to the controlled pattern.
|
|
236
|
-
</details>
|
|
237
|
-
<details><summary>Rendering the same hook twice without a \`key\`</summary>
|
|
238
|
-
If they should be independent, add a unique \`key\`.
|
|
239
|
-
</details>
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## Error & UX guidance
|
|
244
|
-
- **Loading:** early return or inline spinner. Prefer **sticky data** to avoid blanking content.
|
|
245
|
-
- **Errors:** show a banner or inline errors depending on UX; keep previous good state if possible.
|
|
246
|
-
- **Cleanup:** prefer \`clearOnUnmount: true\` as the default.
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## Concurrency patterns
|
|
251
|
-
- **Refresh:** call the action again; combine with sticky data for smooth UX.
|
|
252
|
-
- **Dedupe:** isolate instances with \`key\`.
|
|
253
|
-
- **Parallel:** render two keyed instances; don’t share the same key.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Full examples
|
|
258
|
-
|
|
259
|
-
### Short format (lifecycle-bound)
|
|
260
|
-
${"```tsx"}
|
|
261
|
-
import { use${pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
|
|
262
|
-
import { isPending, isError, isSuccess } from '@intrig/react';
|
|
263
|
-
import { useMemo } from 'react';
|
|
264
|
-
|
|
265
|
-
function ShortExample() {
|
|
266
|
-
const [${respVar}] = use${pascalCase(descriptor.name)}({
|
|
267
|
-
fetchOnMount: true,
|
|
268
|
-
clearOnUnmount: true,
|
|
269
|
-
${requestBodyType ? `body: ${requestBodyVar},` : ''} ${paramsType ? `params: ${paramsVar ?? '{}'},` : 'params: {},'}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
const ${dataVar} = useMemo(() => (isSuccess(${respVar}) ? ${respVar}.data : undefined), [${respVar}]);
|
|
273
|
-
|
|
274
|
-
if (isPending(${respVar})) return <>Loading…</>;
|
|
275
|
-
if (isError(${respVar})) return <>Error: {String(${respVar}.error)}</>;
|
|
276
|
-
return <pre>{JSON.stringify(${dataVar}, null, 2)}</pre>;
|
|
277
|
-
}
|
|
278
|
-
${"```"}
|
|
279
|
-
|
|
280
|
-
<details><summary>Description</summary>
|
|
281
|
-
<p>Compact lifecycle-bound approach. Great for read-only pages that load once and clean up on unmount.</p>
|
|
282
|
-
</details>
|
|
283
|
-
|
|
284
|
-
### Controlled format (explicit actions)
|
|
285
|
-
${"```tsx"}
|
|
286
|
-
import { use${pascalCase(descriptor.name)} } from '@intrig/react/${descriptor.path}/client';
|
|
287
|
-
import { isPending, isError, isSuccess } from '@intrig/react';
|
|
288
|
-
import { useEffect, useMemo } from 'react';
|
|
289
|
-
|
|
290
|
-
function ControlledExample() {
|
|
291
|
-
const [${respVar}, ${actionName}, ${clearName}] = use${pascalCase(descriptor.name)}();
|
|
292
|
-
|
|
293
|
-
useEffect(() => {
|
|
294
|
-
${actionName}(${[requestBodyVar, paramsVar ?? '{}'].filter(Boolean).join(', ')});
|
|
295
|
-
return ${clearName};
|
|
296
|
-
}, [${[''+actionName, clearName].join(', ')}]);
|
|
297
|
-
|
|
298
|
-
const ${dataVar} = useMemo(() => (isSuccess(${respVar}) ? ${respVar}.data : undefined), [${respVar}]);
|
|
299
|
-
|
|
300
|
-
if (isPending(${respVar})) return <>Loading…</>;
|
|
301
|
-
if (isError(${respVar})) return <>An error occurred: {String(${respVar}.error)}</>;
|
|
302
|
-
return <pre>{JSON.stringify(${dataVar}, null, 2)}</pre>;
|
|
303
|
-
}
|
|
304
|
-
${"```"}
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
## Gotchas & Tips
|
|
309
|
-
- Prefer **\`clearOnUnmount: true\`** in most components.
|
|
310
|
-
- Use **\`key\`** for multiple independent instances.
|
|
311
|
-
- Memoize derived values with **\`useMemo\`** to avoid churn.
|
|
312
|
-
- Inline indicators keep the rest of the page interactive.
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
## Reference: State helpers
|
|
317
|
-
${"```ts"}
|
|
318
|
-
if (isPending(${respVar})) { /* show spinner */ }
|
|
319
|
-
if (isError(${respVar})) { /* show error */ }
|
|
320
|
-
if (isSuccess(${respVar})) { /* read ${respVar}.data */ }
|
|
321
|
-
${"```"}
|
|
322
|
-
`
|
|
323
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { mdLiteral, ResourceDescriptor, Schema, typescript} from "@intrig/plugin-sdk"
|
|
2
|
-
import {openApiSchemaToZod} from '../source/type/typeTemplate.js'
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Schema documentation tab builders for React binding.
|
|
7
|
-
* We keep a small, composable API similar to other docs templates.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export async function schemaTypescriptDoc(result: ResourceDescriptor<Schema>) {
|
|
11
|
-
const md = mdLiteral('schema-typescript.md')
|
|
12
|
-
const name = result.data.name
|
|
13
|
-
const source = result.source
|
|
14
|
-
|
|
15
|
-
const {tsType} = openApiSchemaToZod(result.data.schema);
|
|
16
|
-
|
|
17
|
-
const ts = typescript(path.resolve('src', source, 'temp', name, `${name}.ts`))
|
|
18
|
-
|
|
19
|
-
const importContent = await ts`
|
|
20
|
-
import type { ${name} } from '@intrig/react/${source}/components/schemas/${name}';
|
|
21
|
-
`;
|
|
22
|
-
|
|
23
|
-
const codeContent = await ts`
|
|
24
|
-
export type ${name} = ${tsType};
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
return md`
|
|
28
|
-
# Typescript Type
|
|
29
|
-
Use this TypeScript type anywhere you need static typing for this object shape in your app code: component props, function params/returns, reducers, and local state in .ts/.tsx files.
|
|
30
|
-
|
|
31
|
-
## Import
|
|
32
|
-
${'```ts'}
|
|
33
|
-
${importContent}
|
|
34
|
-
${'```'}
|
|
35
|
-
|
|
36
|
-
## Definition
|
|
37
|
-
${'```ts'}
|
|
38
|
-
${codeContent}
|
|
39
|
-
${'```'}
|
|
40
|
-
`
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function schemaJsonSchemaDoc(result: ResourceDescriptor<Schema>) {
|
|
44
|
-
const md = mdLiteral('schema-json.md')
|
|
45
|
-
const name = result.data.name
|
|
46
|
-
const source = result.source
|
|
47
|
-
|
|
48
|
-
const ts = typescript(path.resolve('src', source, 'temp', name, `${name}.ts`))
|
|
49
|
-
|
|
50
|
-
const importContent = await ts`
|
|
51
|
-
import { ${name}_jsonschema } from '@intrig/react/${source}/components/schemas/${name}';
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const codeContent = await ts`
|
|
55
|
-
export const ${name}_jsonschema = ${JSON.stringify(result.data.schema, null, 2) ?? "{}"};
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
return md`
|
|
59
|
-
# JSON Schema
|
|
60
|
-
Use this JSON Schema with tools that consume JSON Schema: UI form builders (e.g. react-jsonschema-form), validators (AJV, validators in backends), and generators.
|
|
61
|
-
|
|
62
|
-
## Import
|
|
63
|
-
${'```ts'}
|
|
64
|
-
${importContent}
|
|
65
|
-
${'```'}
|
|
66
|
-
|
|
67
|
-
## Definition
|
|
68
|
-
${'```ts'}
|
|
69
|
-
${codeContent}
|
|
70
|
-
${'```'}
|
|
71
|
-
`
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function schemaZodSchemaDoc(result: ResourceDescriptor<Schema>) {
|
|
75
|
-
const md = mdLiteral('schema-zod.md')
|
|
76
|
-
const name = result.data.name
|
|
77
|
-
const source = result.source
|
|
78
|
-
|
|
79
|
-
const {zodSchema} = openApiSchemaToZod(result.data.schema);
|
|
80
|
-
|
|
81
|
-
const ts = typescript(path.resolve('src', source, 'temp', name, `${name}.ts`))
|
|
82
|
-
|
|
83
|
-
const importContent = await ts`
|
|
84
|
-
import { ${name}Schema } from '@intrig/react/${source}/components/schemas/${name}';
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
const codeContent = await ts`
|
|
88
|
-
export const ${name}Schema = ${zodSchema};
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
return md`
|
|
92
|
-
# Zod Schema
|
|
93
|
-
Use this Zod schema for runtime validation and parsing: form validation, client/server payload guards, and safe transformations before using or storing data.
|
|
94
|
-
|
|
95
|
-
## Import
|
|
96
|
-
${'```ts'}
|
|
97
|
-
${importContent}
|
|
98
|
-
${'```'}
|
|
99
|
-
|
|
100
|
-
## Definition
|
|
101
|
-
${'```ts'}
|
|
102
|
-
${codeContent}
|
|
103
|
-
${'```'}
|
|
104
|
-
`
|
|
105
|
-
}
|