@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @gears-frontx/react
2
2
 
3
- React bindings and hooks for HAI3 applications. Provides the React integration layer with MFE (Microfrontend) support.
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
- ### HAI3Provider
11
+ ### Gears FrontXProvider
12
12
 
13
- Wrap your app with HAI3Provider to enable all hooks:
13
+ Wrap your app with Gears FrontXProvider to enable all hooks:
14
14
 
15
15
  ```tsx
16
- import { HAI3Provider } from '@gears-frontx/react';
16
+ import { Gears FrontXProvider } from '@gears-frontx/react';
17
17
 
18
18
  function App() {
19
19
  return (
20
- <HAI3Provider>
20
+ <Gears FrontXProvider>
21
21
  <YourApp />
22
- </HAI3Provider>
22
+ </Gears FrontXProvider>
23
23
  );
24
24
  }
25
25
 
26
26
  // With configuration
27
- <HAI3Provider config={{ devMode: true }}>
27
+ <Gears FrontXProvider config={{ devMode: true }}>
28
28
  <YourApp />
29
- </HAI3Provider>
29
+ </Gears FrontXProvider>
30
30
 
31
- // With pre-built app
32
- const app = createHAI3().use(screensets()).build();
33
- <HAI3Provider app={app}>
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
- </HAI3Provider>
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
- #### useHAI3
162
+ #### useFrontX
41
163
 
42
- Access the HAI3 app instance:
164
+ Access the FrontX app instance:
43
165
 
44
166
  ```tsx
45
- import { useHAI3 } from '@gears-frontx/react';
167
+ import { useFrontX } from '@gears-frontx/react';
46
168
 
47
169
  function MyComponent() {
48
- const app = useHAI3();
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 { HAI3_ACTION_LOAD_EXT, HAI3_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
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: HAI3_ACTION_LOAD_EXT, target: 'screen', payload: { extensionId: 'other' } }
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(HAI3_SHARED_PROPERTY_THEME);
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, HAI3_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
283
+ import { useSharedProperty, Gears FrontX_SHARED_PROPERTY_THEME } from '@gears-frontx/react';
162
284
 
163
285
  function ThemedComponent() {
164
- const theme = useSharedProperty(HAI3_SHARED_PROPERTY_THEME);
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, HAI3_ACTION_LOAD_EXT } from '@gears-frontx/react';
297
+ import { useHostAction, Gears FrontX_ACTION_LOAD_EXT } from '@gears-frontx/react';
176
298
 
177
299
  function MyExtension() {
178
- const loadExtension = useHostAction(HAI3_ACTION_LOAD_EXT);
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 bridge={parentBridge}>
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
- <div>
274
- <ExtensionDomainSlot
275
- domainId="screen"
276
- containerRef={screenContainerRef}
277
- fallback={<Loading />}
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 HAI3Provider** - Required for all hooks to work
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. **Lazy load translations** - Use `useScreenTranslations` for screen-level i18n
308
- 4. **Use MFE hooks for extensions** - `useMfeBridge`, `useSharedProperty`, `useHostAction`, `useDomainExtensions`
309
- 5. **NO Layout components here** - Layout and UI components belong in L4 (user's project via CLI scaffolding)
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
- - `HAI3Provider` - Main context provider
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
- - `useHAI3` - Access app instance
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
- - `HAI3Context` - React context (for advanced use)
485
+ - `Gears FrontXContext` - React context (for advanced use)
347
486
  - `MfeContext` - MFE context (for advanced use)
348
487
 
349
488
  ### Types
350
- - `HAI3ProviderProps`
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 { useHAI3 } from '@gears-frontx/react';
526
+ import { useFrontX } from '@gears-frontx/react';
382
527
 
383
528
  function MyComponent() {
384
- const app = useHAI3();
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 (Alternative)**: Use ExtensionDomainSlot
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
- containerRef={screenContainerRef}
415
- fallback={<Loading />}
560
+ extensionId="home"
561
+ loadingComponent={<Loading />}
416
562
  />
417
563
  );
418
564
  }