@gears-frontx/react 0.1.0 → 0.2.0-alpha.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/CLAUDE.md +191 -45
- package/dist/index.cjs +1131 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +418 -119
- package/dist/index.d.ts +418 -119
- package/dist/index.js +977 -242
- package/dist/index.js.map +1 -1
- package/dist/testing-Dlmfu6tE.d.cts +56 -0
- package/dist/testing-Dlmfu6tE.d.ts +56 -0
- package/dist/testing.cjs +77 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +4 -0
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +54 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-Cjd20AT-.d.cts +231 -0
- package/dist/types-Cjd20AT-.d.ts +231 -0
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +2 -2
- package/dist/types.d.ts +2 -2
- package/llms.txt +20 -20
- package/package.json +29 -18
- package/commands/hai3-duplicate-screenset.md +0 -58
- package/commands/hai3-new-component.md +0 -48
- package/commands/hai3-new-mfe.md +0 -166
- package/commands/hai3-new-screen.md +0 -39
- package/commands/hai3-new-screenset.md +0 -47
- package/dist/types-Cp8Zm5s0.d.cts +0 -136
- package/dist/types-Cp8Zm5s0.d.ts +0 -136
package/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @gears-frontx/react
|
|
2
2
|
|
|
3
|
-
React bindings and hooks for
|
|
3
|
+
React bindings and hooks for FrontX applications. Provides the React integration layer with MFE (Microfrontend) support.
|
|
4
4
|
|
|
5
5
|
## React Layer
|
|
6
6
|
|
|
@@ -8,44 +8,166 @@ This package is part of the **React Layer (L3)** - it depends only on @gears-fro
|
|
|
8
8
|
|
|
9
9
|
## Core Concepts
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Gears FrontXProvider
|
|
12
12
|
|
|
13
|
-
Wrap your app with
|
|
13
|
+
Wrap your app with Gears FrontXProvider to enable all hooks:
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
import {
|
|
16
|
+
import { Gears FrontXProvider } from '@gears-frontx/react';
|
|
17
17
|
|
|
18
18
|
function App() {
|
|
19
19
|
return (
|
|
20
|
-
<
|
|
20
|
+
<Gears FrontXProvider>
|
|
21
21
|
<YourApp />
|
|
22
|
-
</
|
|
22
|
+
</Gears FrontXProvider>
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// With configuration
|
|
27
|
-
<
|
|
27
|
+
<Gears FrontXProvider config={{ devMode: true }}>
|
|
28
28
|
<YourApp />
|
|
29
|
-
</
|
|
29
|
+
</Gears FrontXProvider>
|
|
30
30
|
|
|
31
|
-
// With pre-built app
|
|
32
|
-
const app =
|
|
33
|
-
<
|
|
31
|
+
// With pre-built app (host-style shell; host typically also uses queryCache())
|
|
32
|
+
const app = createGears FrontX().use(screensets()).use(queryCache()).build();
|
|
33
|
+
<Gears FrontXProvider app={app}>
|
|
34
34
|
<YourApp />
|
|
35
|
-
</
|
|
35
|
+
</Gears FrontXProvider>
|
|
36
|
+
|
|
37
|
+
// Child MFE app — canonical bootstrap matches src/mfe_packages/*/init.ts:
|
|
38
|
+
// apiRegistry.register / initialize before .build; createGears FrontX().use(effects()).use(queryCacheShared()).use(mock()).build();
|
|
39
|
+
// registerSlice after .build when slices exist.
|
|
40
|
+
const mfeApp = createGears FrontX().use(effects()).use(queryCacheShared()).use(mock()).build();
|
|
41
|
+
<Gears FrontXProvider app={mfeApp}>
|
|
42
|
+
<YourApp />
|
|
43
|
+
</Gears FrontXProvider>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The shared `QueryClient` is created and owned by the `queryCache()` framework plugin at L2.
|
|
47
|
+
`Gears FrontXProvider` resolves that client from the app instance — it does not create its own `QueryClient`.
|
|
48
|
+
|
|
49
|
+
When the host uses `queryCache()` and the child MFE app uses `queryCacheShared()` (with `effects()` and `mock()` on the same chain as in repo MFE inits), both roots join the same shared `QueryClient` while keeping separate React trees. `ThemeAwareReactLifecycle` relies on that shared plugin-owned client through the app instance. If the shared client is missing for a mounted MFE, the lifecycle now fails explicitly instead of silently falling back.
|
|
50
|
+
|
|
51
|
+
For separate roots, build each app with the appropriate query-cache plugin (`queryCache()` for the host, `queryCacheShared()` for child MFE shells) so every tree joins the same cache through plugin composition. Host apps should register domains/extensions during bootstrap; **`ExtensionDomainSlot`** is the preferred host-side renderer for screen slots while the framework wires `mount_ext`.
|
|
52
|
+
|
|
53
|
+
### Data Fetching with Endpoint Descriptors
|
|
54
|
+
|
|
55
|
+
Services define endpoints as descriptors. Components consume them via `useApiQuery` and `useApiMutation`. No manual query keys, no `queryOptions()` calls.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { useApiQuery, useApiMutation, apiRegistry } from '@gears-frontx/react';
|
|
59
|
+
import { AccountsApiService } from '../api/AccountsApiService';
|
|
60
|
+
|
|
61
|
+
function ProfileScreen() {
|
|
62
|
+
const service = apiRegistry.getService(AccountsApiService);
|
|
63
|
+
|
|
64
|
+
// Read — pass descriptor directly
|
|
65
|
+
const { data, isLoading, error } = useApiQuery(service.getCurrentUser);
|
|
66
|
+
|
|
67
|
+
// Read with params
|
|
68
|
+
const { data: user } = useApiQuery(service.getUser({ id: '123' }));
|
|
69
|
+
|
|
70
|
+
// Write with optimistic update
|
|
71
|
+
const { mutateAsync, isPending } = useApiMutation({
|
|
72
|
+
endpoint: service.updateProfile,
|
|
73
|
+
onMutate: async (variables, { queryCache }) => {
|
|
74
|
+
await queryCache.cancel(service.getCurrentUser);
|
|
75
|
+
const snapshot = queryCache.get(service.getCurrentUser);
|
|
76
|
+
queryCache.set(service.getCurrentUser, (old) => ({
|
|
77
|
+
...old, user: { ...old.user, ...variables }
|
|
78
|
+
}));
|
|
79
|
+
return { snapshot };
|
|
80
|
+
},
|
|
81
|
+
onError: (_err, _vars, context, { queryCache }) => {
|
|
82
|
+
if (context?.snapshot) {
|
|
83
|
+
queryCache.set(service.getCurrentUser, context.snapshot);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
onSettled: async (_data, _err, _vars, _ctx, { queryCache }) => {
|
|
87
|
+
await queryCache.invalidate(service.getCurrentUser);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Per-endpoint cache override (rare)
|
|
92
|
+
const { data: config } = useApiQuery(service.getConfig, { staleTime: 0 });
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Cache keys are derived automatically by the service — `QueryCache` methods accept
|
|
97
|
+
endpoint descriptors directly (e.g., `queryCache.get(service.getCurrentUser)`).
|
|
98
|
+
|
|
99
|
+
### SSE Streaming with Stream Descriptors
|
|
100
|
+
|
|
101
|
+
Services declare SSE endpoints as stream descriptors. Components consume them via `useApiStream`, which manages the connection lifecycle automatically (connect on mount, disconnect on unmount).
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { useApiStream, apiRegistry } from '@gears-frontx/react';
|
|
105
|
+
import { ChatApiService } from '../api/ChatApiService';
|
|
106
|
+
|
|
107
|
+
function ChatStream() {
|
|
108
|
+
const service = apiRegistry.getService(ChatApiService);
|
|
109
|
+
|
|
110
|
+
// Latest event only (default mode)
|
|
111
|
+
const { data, status, error } = useApiStream(service.messageStream);
|
|
112
|
+
|
|
113
|
+
// Accumulate all events
|
|
114
|
+
const { events, status: streamStatus } = useApiStream(
|
|
115
|
+
service.messageStream,
|
|
116
|
+
{ mode: 'accumulate' }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Deferred connection (enabled: false)
|
|
120
|
+
const [active, setActive] = useState(false);
|
|
121
|
+
const { data: msg, disconnect } = useApiStream(
|
|
122
|
+
service.messageStream,
|
|
123
|
+
{ enabled: active }
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (status === 'connecting') return <Loading />;
|
|
127
|
+
if (status === 'error') return <Error error={error} />;
|
|
128
|
+
|
|
129
|
+
return <div>{data?.text}</div>;
|
|
130
|
+
}
|
|
36
131
|
```
|
|
37
132
|
|
|
133
|
+
`useApiStream` returns `{ data, events, status, error, disconnect }`:
|
|
134
|
+
- `data` — latest event payload (set in both modes)
|
|
135
|
+
- `events` — all received events when `mode: 'accumulate'`; empty array in `'latest'` mode
|
|
136
|
+
- `status` — connection lifecycle state, `StreamStatus` (see below)
|
|
137
|
+
- `error` — the last connect attempt's error; retained until the next connect attempt resets it, so check `status` rather than `error` truthiness
|
|
138
|
+
- `disconnect()` — close the connection manually
|
|
139
|
+
|
|
140
|
+
#### Status lifecycle
|
|
141
|
+
|
|
142
|
+
`status` is a `StreamStatus`: `'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'`.
|
|
143
|
+
|
|
144
|
+
The TSDoc block on `useApiStream` in `src/hooks/useApiStream.ts` is the detailed and authoritative state-machine reference, including teardown edge cases. This guide summarizes consumer-facing usage and gating.
|
|
145
|
+
|
|
146
|
+
`disconnect()` closes the connection and does not reconnect. There is no reconnect function: to open a new connection, change the descriptor key, change `mode`, or toggle `enabled` back to true.
|
|
147
|
+
|
|
148
|
+
#### The `enabled` option
|
|
149
|
+
|
|
150
|
+
`enabled` (default `true`) gates whether the hook connects at all. While it is false the hook never calls the descriptor's `connect()` and ordinarily holds `status: 'idle'` (calling `disconnect()` while disabled is the one exception — see the TSDoc). Flipping it to true re-runs the connect effect and starts a fresh connection; flipping it back to false tears the connection down and returns `status` to `'idle'`.
|
|
151
|
+
|
|
152
|
+
Tearing down via `enabled` does not clear `data` or `events` — a descriptor-key or `mode` change, or re-enabling, does. A component reading `data` while the stream is disabled still sees the last event from the previous connection.
|
|
153
|
+
|
|
154
|
+
Gating advice, since `'idle'` covers both "not started yet" and "deferred by `enabled`":
|
|
155
|
+
|
|
156
|
+
- Gate the loading state on `'connecting'` alone. Treating `'idle'` as loading renders a permanent spinner for a stream deliberately held at `enabled: false`.
|
|
157
|
+
- Handle `'idle'` as "not started / deferred". A component that both defers and shows loading should key its spinner off its own active flag, not off `'idle'`.
|
|
158
|
+
- Check `status === 'error'` rather than the truthiness of `error` to render failures.
|
|
159
|
+
|
|
38
160
|
### Available Hooks
|
|
39
161
|
|
|
40
|
-
####
|
|
162
|
+
#### useFrontX
|
|
41
163
|
|
|
42
|
-
Access the
|
|
164
|
+
Access the FrontX app instance:
|
|
43
165
|
|
|
44
166
|
```tsx
|
|
45
|
-
import {
|
|
167
|
+
import { useFrontX } from '@gears-frontx/react';
|
|
46
168
|
|
|
47
169
|
function MyComponent() {
|
|
48
|
-
const app =
|
|
170
|
+
const app = useFrontX();
|
|
49
171
|
|
|
50
172
|
// Access MFE-enabled registry
|
|
51
173
|
const extensions = app.screensetsRegistry.getRegisteredExtensions();
|
|
@@ -138,18 +260,18 @@ Access the MFE bridge for child MFEs:
|
|
|
138
260
|
|
|
139
261
|
```tsx
|
|
140
262
|
import { useMfeBridge } from '@gears-frontx/react';
|
|
141
|
-
import {
|
|
263
|
+
import { Gears FrontX_ACTION_LOAD_EXT, Gears FrontX_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
|
|
142
264
|
|
|
143
265
|
function MyExtension() {
|
|
144
266
|
const bridge = useMfeBridge();
|
|
145
267
|
|
|
146
268
|
// Execute actions chain on parent
|
|
147
269
|
await bridge.executeActionsChain({
|
|
148
|
-
action: { type:
|
|
270
|
+
action: { type: Gears FrontX_ACTION_LOAD_EXT, target: 'screen', payload: { extensionId: 'other' } }
|
|
149
271
|
});
|
|
150
272
|
|
|
151
273
|
// Get shared property
|
|
152
|
-
const theme = bridge.getProperty(
|
|
274
|
+
const theme = bridge.getProperty(Gears FrontX_SHARED_PROPERTY_THEME);
|
|
153
275
|
}
|
|
154
276
|
```
|
|
155
277
|
|
|
@@ -158,10 +280,10 @@ function MyExtension() {
|
|
|
158
280
|
Subscribe to shared property changes:
|
|
159
281
|
|
|
160
282
|
```tsx
|
|
161
|
-
import { useSharedProperty,
|
|
283
|
+
import { useSharedProperty, Gears FrontX_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
|
|
162
284
|
|
|
163
285
|
function ThemedComponent() {
|
|
164
|
-
const theme = useSharedProperty(
|
|
286
|
+
const theme = useSharedProperty(Gears FrontX_SHARED_PROPERTY_THEME);
|
|
165
287
|
|
|
166
288
|
return <div style={{ backgroundColor: theme?.primaryColor }}>...</div>;
|
|
167
289
|
}
|
|
@@ -172,10 +294,10 @@ function ThemedComponent() {
|
|
|
172
294
|
Invoke actions on the host application:
|
|
173
295
|
|
|
174
296
|
```tsx
|
|
175
|
-
import { useHostAction,
|
|
297
|
+
import { useHostAction, Gears FrontX_ACTION_LOAD_EXT } from '@gears-frontx/react';
|
|
176
298
|
|
|
177
299
|
function MyExtension() {
|
|
178
|
-
const loadExtension = useHostAction(
|
|
300
|
+
const loadExtension = useHostAction(Gears FrontX_ACTION_LOAD_EXT);
|
|
179
301
|
|
|
180
302
|
const handleClick = () => {
|
|
181
303
|
loadExtension({ extensionId: 'other' });
|
|
@@ -254,7 +376,7 @@ import { MfeProvider } from '@gears-frontx/react';
|
|
|
254
376
|
|
|
255
377
|
function MfeHost() {
|
|
256
378
|
return (
|
|
257
|
-
<MfeProvider
|
|
379
|
+
<MfeProvider value={{ bridge: parentBridge, extensionId: 'home', domainId: 'screen' }}>
|
|
258
380
|
<ExtensionContainer />
|
|
259
381
|
</MfeProvider>
|
|
260
382
|
);
|
|
@@ -270,17 +392,27 @@ import { ExtensionDomainSlot } from '@gears-frontx/react';
|
|
|
270
392
|
|
|
271
393
|
function LayoutScreen() {
|
|
272
394
|
return (
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
</div>
|
|
395
|
+
<ExtensionDomainSlot
|
|
396
|
+
registry={registry}
|
|
397
|
+
domainId="screen"
|
|
398
|
+
extensionId="home"
|
|
399
|
+
loadingComponent={<Loading />}
|
|
400
|
+
/>
|
|
280
401
|
);
|
|
281
402
|
}
|
|
282
403
|
```
|
|
283
404
|
|
|
405
|
+
`ExtensionDomainSlot` is the preferred host-side renderer for screen MFEs. It
|
|
406
|
+
owns mount/unmount and loading/error UI while host bootstrap handles domain
|
|
407
|
+
registration and shared property setup before slot-driven `mount_ext`.
|
|
408
|
+
The host `QueryClient` is reused by MFE React trees through
|
|
409
|
+
`queryCacheShared()`, not by embedding a `QueryClient` on `MfeMountContext`
|
|
410
|
+
or expecting host React context inside Shadow DOM.
|
|
411
|
+
|
|
412
|
+
When the domain's `ContainerProvider` must point at the same DOM node rendered
|
|
413
|
+
by the slot, pass `containerRef` to `ExtensionDomainSlot` and share that ref
|
|
414
|
+
with `RefContainerProvider`.
|
|
415
|
+
|
|
284
416
|
#### RefContainerProvider
|
|
285
417
|
|
|
286
418
|
Provide container references for MFE mounting:
|
|
@@ -302,11 +434,14 @@ function Layout() {
|
|
|
302
434
|
|
|
303
435
|
## Key Rules
|
|
304
436
|
|
|
305
|
-
1. **Wrap with
|
|
437
|
+
1. **Wrap with Gears FrontXProvider** - Required for all hooks to work
|
|
306
438
|
2. **Use hooks for state access** - Don't import selectors directly from @gears-frontx/framework
|
|
307
|
-
3. **
|
|
308
|
-
4. **
|
|
309
|
-
5. **
|
|
439
|
+
3. **Use endpoint descriptors for data** - `useApiQuery(service.endpoint)` for REST, `useApiStream(service.stream)` for SSE — not `queryOptions()` or manual key factories
|
|
440
|
+
4. **Service is the cache contract** - The service IS the data layer; cache keys are derived automatically
|
|
441
|
+
5. **QueryCache uses descriptors** - `queryCache.get(service.endpoint)`, not raw key arrays
|
|
442
|
+
6. **Lazy load translations** - Use `useScreenTranslations` for screen-level i18n
|
|
443
|
+
7. **Use MFE hooks for extensions** - `useMfeBridge`, `useSharedProperty`, `useHostAction`, `useDomainExtensions`
|
|
444
|
+
8. **NO Layout components here** - Layout and UI components belong in L4 (user's project via CLI scaffolding)
|
|
310
445
|
|
|
311
446
|
## Re-exports
|
|
312
447
|
|
|
@@ -323,15 +458,19 @@ This allows users to import everything from `@gears-frontx/react` without needin
|
|
|
323
458
|
## Exports
|
|
324
459
|
|
|
325
460
|
### Components
|
|
326
|
-
- `
|
|
461
|
+
- `Gears FrontXProvider` - Main context provider
|
|
327
462
|
- `MfeProvider` - MFE context provider
|
|
328
463
|
- `ExtensionDomainSlot` - Domain slot renderer
|
|
329
464
|
- `RefContainerProvider` - Container reference provider
|
|
330
465
|
|
|
331
466
|
### Hooks
|
|
332
|
-
- `
|
|
467
|
+
- `useFrontX` - Access app instance
|
|
333
468
|
- `useAppDispatch` - Typed dispatch
|
|
334
469
|
- `useAppSelector` - Typed selector
|
|
470
|
+
- `useApiQuery` - Declarative data fetch from endpoint descriptor; returns `ApiQueryResult<TData>`
|
|
471
|
+
- `useApiMutation` - Declarative mutation with endpoint descriptor and optimistic update support; returns `ApiMutationResult<TData>`
|
|
472
|
+
- `useApiStream` - Declarative SSE streaming from stream descriptor; returns `ApiStreamResult<TEvent>`
|
|
473
|
+
- `useQueryCache` - Restricted query cache access (accepts descriptors or raw keys)
|
|
335
474
|
- `useTranslation` - Translation utilities
|
|
336
475
|
- `useScreenTranslations` - Screen translation loading
|
|
337
476
|
- `useTheme` - Theme utilities
|
|
@@ -343,14 +482,20 @@ This allows users to import everything from `@gears-frontx/react` without needin
|
|
|
343
482
|
- `useActivePackage` - Subscribe to active GTS package
|
|
344
483
|
|
|
345
484
|
### Context
|
|
346
|
-
- `
|
|
485
|
+
- `Gears FrontXContext` - React context (for advanced use)
|
|
347
486
|
- `MfeContext` - MFE context (for advanced use)
|
|
348
487
|
|
|
349
488
|
### Types
|
|
350
|
-
- `
|
|
489
|
+
- `Gears FrontXProviderProps`
|
|
490
|
+
- `ApiQueryResult<TData>` - Gears FrontX-owned query result type (data, error, isLoading, refetch, etc.)
|
|
491
|
+
- `ApiMutationResult<TData>` - Gears FrontX-owned mutation result type (mutateAsync, isPending, error, reset, etc.)
|
|
492
|
+
- `ApiStreamResult<TEvent>` - Gears FrontX-owned stream result type (data, events, status, error, disconnect)
|
|
493
|
+
- `ApiStreamOptions` - Stream hook options (mode, enabled)
|
|
494
|
+
- `QueryCache` - Restricted cache interface (accepts descriptors or raw keys)
|
|
495
|
+
- `MutationCallbackContext` - Context with queryCache injected into mutation callbacks
|
|
351
496
|
- `MfeProviderProps`, `ExtensionDomainSlotProps`
|
|
352
497
|
- `UseTranslationReturn`, `UseThemeReturn`
|
|
353
|
-
- All types from @gears-frontx/framework
|
|
498
|
+
- All types from @gears-frontx/framework (including `EndpointDescriptor`, `MutationDescriptor`, `StreamDescriptor`, `StreamStatus`)
|
|
354
499
|
|
|
355
500
|
## Migration from Legacy API
|
|
356
501
|
|
|
@@ -378,10 +523,10 @@ function MyComponent() {
|
|
|
378
523
|
|
|
379
524
|
**NEW**: Mount extension using app actions
|
|
380
525
|
```tsx
|
|
381
|
-
import {
|
|
526
|
+
import { useFrontX } from '@gears-frontx/react';
|
|
382
527
|
|
|
383
528
|
function MyComponent() {
|
|
384
|
-
const app =
|
|
529
|
+
const app = useFrontX();
|
|
385
530
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
386
531
|
|
|
387
532
|
const handleNavigate = async () => {
|
|
@@ -403,16 +548,17 @@ function MyComponent() {
|
|
|
403
548
|
}
|
|
404
549
|
```
|
|
405
550
|
|
|
406
|
-
**NEW (
|
|
551
|
+
**NEW (Preferred for host screen slots)**: Use ExtensionDomainSlot
|
|
407
552
|
```tsx
|
|
408
553
|
import { ExtensionDomainSlot } from '@gears-frontx/react';
|
|
409
554
|
|
|
410
555
|
function MyComponent() {
|
|
411
556
|
return (
|
|
412
557
|
<ExtensionDomainSlot
|
|
558
|
+
registry={app.screensetsRegistry}
|
|
413
559
|
domainId="screen"
|
|
414
|
-
|
|
415
|
-
|
|
560
|
+
extensionId="home"
|
|
561
|
+
loadingComponent={<Loading />}
|
|
416
562
|
/>
|
|
417
563
|
);
|
|
418
564
|
}
|