@usearete/react 0.1.5 → 0.3.0

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 CHANGED
@@ -1,108 +1,397 @@
1
1
  # Arete React SDK
2
2
 
3
- React SDK for real-time Solana program data streaming from Arete.
3
+ React hooks and provider for consuming Arete stacks in React applications.
4
4
 
5
- Built on top of [`@usearete/sdk`](https://www.npmjs.com/package/@usearete/sdk), the pure TypeScript core SDK.
5
+ Built on top of [`@usearete/sdk`](https://www.npmjs.com/package/@usearete/sdk), the framework-agnostic TypeScript core SDK.
6
6
 
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @usearete/react
10
+ npm install @usearete/react @usearete/sdk zod
11
11
  ```
12
12
 
13
- > **Not using React?** Use [`@usearete/sdk`](../core/README.md) directly for Vue, Svelte, Node.js, or vanilla JavaScript.
13
+ `@usearete/react` installs `zustand` as a normal dependency, so do not install it separately. React remains an application dependency. Generated consumers install the core SDK and Zod because generated stack files import their types and schemas directly.
14
14
 
15
- ## Usage
15
+ ### Hooks linting
16
16
 
17
- ### Basic Setup
17
+ Arete's fluent `.use()`, `.useOne()`, and `.useMutation()` calls are real React
18
+ hooks, but the standard Hooks rule cannot identify deeply nested member calls.
19
+ The package ships `@usearete/react/eslint-plugin.cjs` with an
20
+ `arete/fluent-hooks` rule for flat ESLint configurations. The ORE template also
21
+ configures the equivalent bundled rule for ESLint 8. Use it together with
22
+ `react-hooks/rules-of-hooks`, not instead of the standard rule.
23
+
24
+ ```js
25
+ // eslint.config.js
26
+ import areteHooks from '@usearete/react/eslint-plugin.cjs';
27
+
28
+ export default [{
29
+ plugins: { arete: areteHooks },
30
+ rules: { 'arete/fluent-hooks': 'error' },
31
+ }];
32
+ ```
33
+
34
+ > Not using React? Use [`@usearete/sdk`](../core/README.md) directly.
35
+
36
+ ## Quick Start
37
+
38
+ Keep the generated stack explicit at the provider and consumer boundaries. Equivalent calls share one provider-managed client and store:
18
39
 
19
40
  ```tsx
20
- import { AreteProvider, useArete, defineStack } from '@usearete/react';
41
+ import { AreteProvider, useArete } from '@usearete/react';
42
+ import { ORE_STREAM_STACK } from './generated/ore-stack';
43
+
44
+ const publishableKey = import.meta.env.VITE_ARETE_PUBLISHABLE_KEY;
45
+ if (!publishableKey) throw new Error('VITE_ARETE_PUBLISHABLE_KEY is required');
46
+
47
+ function Dashboard() {
48
+ const arete = useArete(ORE_STREAM_STACK);
49
+ const board = arete.views.OreBoard.state.use({
50
+ address: arete.addresses.board(),
51
+ });
52
+ const roundId = board.data?.state.roundId;
53
+ const round = arete.views.OreRound.state.use(
54
+ roundId == null ? undefined : { roundId },
55
+ );
21
56
 
22
- const myStack = defineStack({
23
- // Your stack configuration
24
- });
57
+ if (arete.status === 'error') {
58
+ return <button onClick={() => void arete.retry()}>Reconnect</button>;
59
+ }
60
+ if (board.status !== 'ready' || round.status !== 'ready') {
61
+ return <p>Connecting...</p>;
62
+ }
63
+
64
+ return (
65
+ <div>
66
+ {arete.socketIssue && <p>{arete.socketIssue.message}</p>}
67
+ <p>Round {round.data?.id.roundId?.toString() ?? 'unavailable'}</p>
68
+ </div>
69
+ );
70
+ }
25
71
 
26
- function App() {
72
+ export default function App() {
27
73
  return (
28
- <AreteProvider config={{ /* your config */ }}>
29
- <MyComponent />
74
+ <AreteProvider
75
+ autoConnect
76
+ stack={ORE_STREAM_STACK}
77
+ auth={{ publishableKey }}
78
+ >
79
+ <Dashboard />
30
80
  </AreteProvider>
31
81
  );
32
82
  }
83
+ ```
33
84
 
34
- function MyComponent() {
35
- const stack = useArete(myStack);
36
- // Use your stack
85
+ The hosted ORE stack requires this publishable key for authentication and rate limiting. Read-only browser viewing does not require a wallet, but it is not unauthenticated.
86
+
87
+ Single-stack applications can remove the repeated constant without ambient types by binding the React API once:
88
+
89
+ ```tsx
90
+ import { createAreteReact } from '@usearete/react';
91
+ import { ORE_STREAM_STACK } from './generated/ore-stack';
92
+
93
+ export const {
94
+ Provider: OreProvider,
95
+ useArete: useOre,
96
+ } = createAreteReact(ORE_STREAM_STACK);
97
+ ```
98
+
99
+ `useOre()` remains explicit about the application dependency. Multi-stack apps can keep using `AreteProvider` and `useArete(stack)` directly.
100
+
101
+ Provider-default `useArete()` calls remain available. For full type inference, register the stack type once (e.g. in `src/arete.d.ts`):
102
+
103
+ ```ts
104
+ import type { OreStreamStack } from './generated/ore-stack';
105
+
106
+ declare module '@usearete/react' {
107
+ interface AreteDefaultStackRegistry {
108
+ defaultStack: OreStreamStack;
109
+ }
37
110
  }
38
111
  ```
39
112
 
40
- ### Core Features
113
+ Multi-stack apps can keep passing the stack explicitly: `useArete(stack, options)` always wins over the provider default.
114
+
115
+ ## Provider
41
116
 
42
- - **Real-time Data Streaming**: Subscribe to Solana program state changes
43
- - **React Integration**: Hooks-based API for easy integration with React applications
44
- - **State Management**: Built-in state management with Zustand
45
- - **Type Safety**: Full TypeScript support with comprehensive type definitions
46
- - **View Definitions**: Create state and list views for your data
47
- - **Transaction Handling**: Define and execute transactions with hooks
48
- - **Single Item Queries**: Type-safe single item fetching with `take: 1` or `useOne()`
117
+ `AreteProvider` manages connected clients for descendant hooks.
49
118
 
50
- ### API
119
+ Supported props:
51
120
 
52
- #### Providers
121
+ - `stack` — default stack for argument-less `useArete()` calls
122
+ - `stackOptions` — default client lookup options (`url`, `httpUrl`, `transport`, `programs`) for the provider stack, including explicit `useArete(providerStack)` calls
123
+ - `autoConnect` — defaults to `true`; controls only whether the provider starts the initial connection
124
+ - `autoReconnect` — defaults to `true`; controls only recovery after an established connection is lost
125
+ - `wallet`
126
+ - `auth`
127
+ - `fetch`
128
+ - `validateFrames` — set `false` to suppress rejected-frame warnings; generated schemas still normalize and validate entities
129
+ - `onFrameValidationError` — structured callback for generated-schema rejections
130
+ - `reconnectIntervals`
131
+ - `maxReconnectAttempts`
132
+ - `maxEntriesPerView`
133
+ - `flushIntervalMs`
53
134
 
54
- - `AreteProvider` - Root provider for Arete configuration
135
+ These are provider-wide defaults. Per-call overrides stay on the hook call and always win. Connection settings are captured when a shared client is created; changing provider props does not mutate that client. `wallet` updates are synchronized in place. For other changed settings, call `retry()` to replace the shared client using the latest provider configuration.
55
136
 
56
- #### Hooks
137
+ The lifecycle flags are intentionally independent. `autoConnect={false}` leaves initial connection ownership to the application. It does not disable automatic recovery for a connection the application establishes, but `canRetry` remains false because provider-owned replacement attempts would also stay disconnected; use the client lifecycle directly. `autoReconnect={false}` does not suppress the initial connection; it disables only automatic recovery after that connection is lost.
57
138
 
58
- - `useArete` - Main hook for accessing stack functionality
59
- - `useAreteContext` - Access the runtime context directly
139
+ ## `useArete(stack?, options?)`
60
140
 
61
- #### View Methods
141
+ `useArete` returns the connected React surface for a stack. With no arguments it resolves the provider's `stack`/`stackOptions`. Passing the same stack explicitly keeps the provider's `stackOptions`; per-call options override them, while another explicit stack is isolated from the provider stack's options. It throws a descriptive error when no stack is passed and no default is configured.
62
142
 
63
- - `.use()` - Subscribe to view data (returns `T[]` for lists, `T` for state)
64
- - `.use({ take: 1 })` - Subscribe to single item with type narrowing (returns `T | undefined`)
65
- - `.useOne()` - Convenience method for single item queries (returns `T | undefined`)
143
+ - `views`
144
+ - `programs`
145
+ - `queries`
146
+ - `read` — callable stack reads with `.use(...args, options?)` React hooks
147
+ - `chain`
148
+ - `client`
149
+ - `reads` — deprecated alias of `read`
150
+ - `status` — preferred connection state for UI; includes the initial client-creation window
151
+ - `connectionState` — raw state from the connected client
152
+ - `isConnected`
153
+ - `isLoading`
154
+ - `canRetry` — true when `retry()` can start a new shared connection attempt
155
+ - `error`
156
+ - `socketIssue` — the latest structured server WebSocket issue, if any
157
+ - `retry()` — replaces the failed shared client; every matching `useArete` consumer observes the connecting window and adopts the same replacement
66
158
 
67
- #### Factory Functions
159
+ Supported hook options:
68
160
 
69
- - `defineStack` - Define a new stack configuration
70
- - `createStateView` - Create a state view
71
- - `createListView` - Create a list view
72
- - `createRuntime` - Create a runtime instance
161
+ - `url`
162
+ - `httpUrl`
163
+ - `transport`
164
+ - `programs`
73
165
 
74
- #### Utilities
166
+ All options are optional. By default the client connects to the endpoints embedded in the generated stack definition (`stack.endpoints`), so `useArete(STACK)` with no options is the common case.
75
167
 
76
- - `ConnectionManager` - Manage WebSocket connections
168
+ Notes:
77
169
 
78
- ## Relationship with @usearete/sdk
170
+ - `transport: 'http'` disables streaming view subscriptions, but HTTP-backed surfaces like `queries`, `chain`, and program reads still work.
171
+ - Attached `programs` are keyed by value (program name and id): passing a fresh object literal each render still resolves to the same client.
172
+ - Repeated `useArete` calls with the same stack object and options share one provider-managed client and store. It is safe for sibling components to call the hook independently.
79
173
 
80
- This package depends on and re-exports the core `@usearete/sdk` package. The core SDK provides:
174
+ ## Views
81
175
 
82
- - `Arete` - Main client class
83
- - `ConnectionManager` - WebSocket connection handling
84
- - `EntityStore` - State management
85
- - AsyncIterable-based streaming APIs
176
+ View hooks return a discriminated `ViewHookResult<T>`. `status` and `isEmpty`
177
+ narrow the associated data and error fields:
86
178
 
87
- The React SDK adds:
179
+ ```ts
180
+ if (round.status === 'error') {
181
+ console.error(round.error); // Error
182
+ } else if (round.status === 'ready' && !round.isEmpty) {
183
+ console.log(round.data.id.roundId); // entity, not undefined
184
+ }
185
+ ```
88
186
 
89
- - `AreteProvider` - React context provider
90
- - `useArete` - Main hook for accessing stacks
91
- - `useConnectionState` - Connection monitoring hook
92
- - `defineStack`, `createStateView`, `createListView` - React-friendly factories
187
+ `status` distinguishes "no client yet" (`connecting`) from "subscribed, waiting for the first frame" (`subscribing`). `isPending` covers both states, `isReady` marks a usable ready result, and `isEmpty` distinguishes a ready hook with no entity or list entries from loading. Errors may retain previously committed data, but `isReady` becomes false. `isLoading` remains for compatibility.
93
188
 
94
- If you need low-level access, you can import directly from the core:
189
+ Use the connection `status` together with `socketIssue` and `retry()` for recovery UI:
95
190
 
96
- ```typescript
97
- import { Arete, ConnectionManager } from '@usearete/react';
98
- // or
99
- import { Arete, ConnectionManager } from '@usearete/sdk';
191
+ ```tsx
192
+ const arete = useArete(ORE_STREAM_STACK);
193
+
194
+ if (arete.canRetry) {
195
+ return <button onClick={() => void arete.retry()}>Reconnect</button>;
196
+ }
197
+
198
+ return <ConnectionBadge status={arete.status} issue={arete.socketIssue} />;
100
199
  ```
101
200
 
102
- ## License
201
+ The view hook objects themselves are refresh targets — they refresh that view's *active* subscriptions without you holding a hook result, and are a no-op when nothing is subscribed:
103
202
 
104
- MIT
203
+ ```ts
204
+ await arete.views.OreRound.state.refresh({ roundId }); // one keyed subscription
205
+ await arete.views.OreRound.state.refresh(); // every active subscription of the view
206
+ await arete.views.OreRound.latest.refresh(); // list views
207
+
208
+ // Handy in mutation reconciliation — the component doesn't need its own
209
+ // subscriptions just to refresh them:
210
+ claim.submit(args, {
211
+ reconcile: { refresh: [arete.views.OreBoard.state, arete.views.OreMiner.state, preview] },
212
+ });
213
+ ```
214
+
215
+ When several resources feed one status line, aggregate them with `summarizeStatuses` instead of hand-rolling loading/error lists:
216
+
217
+ ```tsx
218
+ const streams = summarizeStatuses({ Board: board, Round: round, Miner: authority && miner });
219
+ // streams.loading → names still loading, e.g. ['Board']
220
+ // streams.refreshing → names resynchronizing committed data
221
+ // streams.errors → 'Name: message' strings
222
+ // streams.isLoading / streams.isRefreshing / streams.hasError
223
+ ```
224
+
225
+ Falsy entries (`authority && miner`) are skipped.
226
+
227
+ List views support:
228
+
229
+ - `.use()`
230
+ - `.use({ take: 1 })`
231
+ - `.useOne()`
232
+
233
+ Put list query configuration (`take`, `skip`, `key`, `partition`, `filters`, `after`, `withSnapshot`, `snapshotLimit`, `schema`, and `onSchemaValidationError`) in the first params object. For compatibility, fields also accepted by view options are honored from the second argument when absent from params; params win when both are supplied. Keep `enabled` and `initialData` in the second argument. State hooks accept one entity as `initialData`, list hooks accept an entity array, and `useOne` accepts one entity.
234
+
235
+ `onSchemaValidationError(diagnostic)` observes entities rejected by a caller-supplied schema. The diagnostic is `{ view, key?, entity, error }`. Accepted entities and existing filtering behavior are unchanged.
236
+
237
+ Protocol v2 gives each parameter set independent ordered membership. Different windows and filters on one view can coexist, while equivalent normalized queries share one reference-counted wire subscription:
238
+
239
+ ```tsx
240
+ const firstPage = views.OreRound.latest.use({ take: 10 });
241
+ const secondPage = views.OreRound.latest.use({ take: 10, skip: 10 });
242
+
243
+ if (firstPage.isLoading) return <p>Loading...</p>;
244
+
245
+ return (
246
+ <>
247
+ {firstPage.isRefreshing && <p>Refreshing...</p>}
248
+ <RoundList rounds={firstPage.data ?? []} />
249
+ <RoundList rounds={secondPage.data ?? []} />
250
+ </>
251
+ );
252
+ ```
253
+
254
+ During reconnect, hooks preserve their committed data and set `isRefreshing`. A completed authoritative snapshot then atomically replaces membership for that exact query, including an empty result.
255
+
256
+ `await result.refresh()` resolves after the refreshed subscription's next complete snapshot is committed. Registration, send, and subscription failures reject, clear `isRefreshing`, and appear in `result.error`. A view refresh with no active matching subscription remains a no-op.
257
+
258
+ State views support:
259
+
260
+ - `.use(key)` — subscribe to one keyed entity using the generated key type (for example `{ roundId: bigint }`)
261
+ - `.use(undefined)` — disabled: no subscription, `{ data: undefined, isLoading: false }`. Useful while a wallet is not connected: `views.OreMiner.state.use(authority ? { authority } : undefined)`.
105
262
 
106
- ## Author
263
+ Keyed hooks only expose data for the key you passed: when the key changes, `data` becomes `undefined` (and `isLoading` true) until the new key's snapshot arrives. Components never need to re-verify returned data against their inputs.
107
264
 
108
- Arete Team
265
+ `initialData` is trusted as immediately usable ready data. The first live store update or completed empty snapshot replaces it; an empty list settles as `[]` and an absent state entity settles as `undefined`. Do not seed transaction-authorizing data unless the application is willing to trust that seed.
266
+
267
+ > **BigInt props in development:** entities contain bigints, and React ≤19.2's dev-mode performance track calls `JSON.stringify` on changed props — it crashes on bigint *arrays and objects* (scalar bigints are fine), wedging the app after the first live update. Prefer stack-provided UI fields when they exist (e.g. `deployedPerSquareUi` next to `deployedPerSquare`), use `stringifyBigints(value)` for a typed structural bigint→string conversion of the rest (raw account snapshots are the common case), or upgrade to React 19.3+ where this is fixed.
268
+
269
+ ## Reads
270
+
271
+ Stacks can expose one-shot read functions (e.g. RPC-backed previews). Each one gets a React hook:
272
+
273
+ ```tsx
274
+ const preview = arete.read.solClaimPreview.use(authority);
275
+ const quote = arete.read.quoteManualDeployment.use(
276
+ input,
277
+ { debounceMs: 300 },
278
+ );
279
+ ```
280
+
281
+ - Arguments form the cache key — the read re-runs when they change, and the previous arguments' data is never exposed while the fresh result is loading.
282
+ - The hook is disabled while any required argument is `null`/`undefined`, so no `enabled` flag or non-null assertions are needed.
283
+ - `.use(...args, { debounceMs, initialData })` adds React-only behavior with options last. Debounce applies to automatic argument changes; `refresh()` always runs immediately. When options follow an omitted optional read argument, pass `undefined` in that argument's position.
284
+ - `status` is `'disabled' | 'connecting' | 'loading' | 'ready' | 'refreshing' | 'error'`. The existing loading and refreshing booleans remain available.
285
+ - `isPending`, `isReady`, and `isEmpty` cover the common UI branches without comparing status strings.
286
+ - `preview.refresh()` re-runs the read on demand, typically after a mutation (see below).
287
+
288
+ The same functions remain callable for imperative access: `await arete.read.solClaimPreview(authority)`. The old `arete.reads` namespace remains as a deprecated alias.
289
+
290
+ ## Programs, Queries, and Chain
291
+
292
+ `useArete` mirrors the connected core client surface:
293
+
294
+ - `programs.<program>.raw.<instruction>` for raw typed instructions
295
+ - `programs.<program>.instructions`, `.transactions`, and `.flows` for semantic operations
296
+ - `programs.<program>.accounts` and `programs.<program>.queries` for HTTP reads
297
+ - `queries` for stack-level HTTP queries
298
+ - `chain` for chain helpers
299
+
300
+ Raw instruction hooks preserve `.execute`, `.build`, and `.useMutation()`.
301
+
302
+ Semantic operation hooks preserve:
303
+
304
+ - `.execute`
305
+ - `.prepare`
306
+ - `.useMutation()`
307
+
308
+ Mutation hooks expose `mutate(args, options)` for event handlers. It records
309
+ errors in hook state and does not return a promise. Use the equivalent rejecting
310
+ `mutateAsync(args, options)` or `submit(args, options)` when the caller must
311
+ await or compose the result.
312
+
313
+ Program and read hooks are safe to call while the client is still connecting: mutations throw "Arete client is not connected" if submitted early, and reads stay disabled.
314
+
315
+ ## Safe amount parsing
316
+
317
+ `safeToRawAmount(input, decimals)` is re-exported by `@usearete/react` for form validation. Unlike `toRawAmount`, it reports invalid input without throwing:
318
+
319
+ ```tsx
320
+ import { safeToRawAmount } from '@usearete/react';
321
+
322
+ const parsed = safeToRawAmount({ ui: amountText }, 9);
323
+ if (!parsed.success) {
324
+ setAmountError(String(parsed.error));
325
+ return;
326
+ }
327
+
328
+ const rawAmount = parsed.data;
329
+ ```
330
+
331
+ The return type is `{ success: true, data } | { success: false, error }`.
332
+
333
+ ## Default reconciliation
334
+
335
+ Mutations from generated program hooks **reconcile against the stream by default**: after the transaction confirms on-chain, the hook waits until the stack has processed the highest confirmed receipt slot before settling (30s timeout). HTTP-only mutations and results without a receipt slot skip this step. The processed-slot watermark does not promise that a particular entity changed; use `refresh` for the views or one-shot reads that must be re-read.
336
+
337
+ Per-submit options:
338
+
339
+ ```tsx
340
+ // Opt out entirely:
341
+ deploy.submit(args, { reconcile: false });
342
+
343
+ // Keep the default, but also refresh views and one-shot reads afterwards.
344
+ // `refresh` accepts view hook objects, view/read hook results, or plain
345
+ // callbacks:
346
+ claim.submit(args, {
347
+ reconcile: { refresh: [arete.views.OreBoard.state, miner, preview] },
348
+ });
349
+
350
+ // Custom timeout:
351
+ deploy.submit(args, { reconcile: { timeoutMs: 10_000 } });
352
+
353
+ // Full control — a function replaces the default:
354
+ deploy.submit(args, { reconcile: async (context) => { /* ... */ } });
355
+ ```
356
+
357
+ The mutation `phase` field is the discriminated status to branch on for busy labels (`'preparing'`, `'awaiting-wallet'`, `'submitted'`, `'confirmed'`, `'reconciling'`, `'reconciled'`, plus the failure outcomes); `status` remains `'pending'` until the mutation settles. Generated operation hooks publish each completed receipt and signature during `submitted`, before the final operation receipt resolves. `onConfirmed` runs immediately after chain confirmation; `onSuccess` runs only after successful reconciliation (or immediately when reconciliation is disabled/skipped). Reconciliation failure, including a refreshed snapshot timeout, is exposed through `reconciliationError`, does not call `onSuccess`, and does not reject the confirmed `submit()` result. The confirmed transaction remains landed. `retryReconciliation()` repeats only post-confirmation watermark/refresh work using the saved receipts and targets; it never rebuilds, signs, or submits. Convenience booleans (`isPreparing`, `isAwaitingWallet`, `isReconciling`, `canRetryReconciliation`) remain available.
358
+
359
+ ## Migration notes (0.3 → next)
360
+
361
+ - `AreteProvider` accepts a default `stack` and `stackOptions`; `useArete()` may then be called with no arguments. Register the stack type via `AreteDefaultStackRegistry` for full inference.
362
+ - View hook results expose a `status` field (`'disabled' | 'connecting' | 'subscribing' | 'ready' | 'error'`).
363
+ - View hook objects (`arete.views.X.state` / `.list`) expose `refresh(key?)` and can be passed directly in `reconcile: { refresh: [...] }`.
364
+ - New `summarizeStatuses(namedResults)` status aggregator; `summarizeViews` remains as a deprecated alias.
365
+ - New `stringifyBigints(value)` display helper (also re-exported from `@usearete/sdk`).
366
+ - `UseAreteResult`'s second type parameter defaults to `undefined`.
367
+ - The duplicate low-level `useView` and `useEntity` hooks were removed. Use generated `arete.views.<Entity>.<view>.use(...)` hooks.
368
+
369
+ ## Migration notes (0.2 → 0.3)
370
+
371
+ - **Generated-hook mutations reconcile by default.** `submit()` now resolves after stream reconciliation instead of right after confirmation. Timeouts settle as `confirmed-unreconciled` rather than failing. Pass `reconcile: false` to restore the old behavior.
372
+ - **State view keys are required and generated.** Pass the generated key object, or explicitly pass `undefined`/`null` to disable the hook. Zero-argument state subscriptions are no longer supported, and malformed keys throw the shared core serializer error.
373
+ - **Disabled view hooks report `isLoading: false`.** Previously `enabled: false` still started in a loading state.
374
+ - New additive `UseMutationResult` fields: `isPreparing`, `isAwaitingWallet`, `isReconciling`.
375
+ - New mutation callback: `onConfirmed`, for work that must run after chain confirmation even if stream reconciliation later fails.
376
+ - `useArete` now exposes `socketIssue` and deduplicated `retry()`.
377
+ - Stack reads expose imperative calls and hooks together under `arete.read.<name>`; `arete.reads` remains a deprecated alias.
378
+ - Repeated `useArete` calls now share a provider-managed client when stack and option identities match; remove component-local connection ownership workarounds.
379
+
380
+ ## Low-Level Hooks
381
+
382
+ The React SDK also exports:
383
+
384
+ - `useConnectionState`
385
+ - `useAreteContext`
386
+
387
+ These accept the same client lookup overrides when you need to target a non-default client.
388
+
389
+ ## Relationship with `@usearete/sdk`
390
+
391
+ `@usearete/react` re-exports selected core APIs and types for convenience, but it is not a complete mirror of the core package.
392
+
393
+ If you need the full low-level surface, import directly from `@usearete/sdk`.
394
+
395
+ ## License
396
+
397
+ MIT