@usearete/react 0.2.0 → 0.4.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/dist/index.cjs ADDED
@@ -0,0 +1,2367 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var sdk = require('@usearete/sdk');
5
+ var zustand = require('zustand');
6
+
7
+ const DEFAULT_FLUSH_INTERVAL_MS = 16;
8
+
9
+ class ZustandAdapter {
10
+ constructor(_config = {}) {
11
+ this.updateCallbacks = new Set();
12
+ this.richUpdateCallbacks = new Set();
13
+ this.accessOrder = new Map();
14
+ this.store = zustand.create((set) => ({
15
+ entities: new Map(),
16
+ viewConfigs: new Map(),
17
+ connectionState: 'disconnected',
18
+ lastError: undefined,
19
+ _set: (viewPath, key, data) => {
20
+ set((state) => {
21
+ const newEntities = new Map(state.entities);
22
+ const viewMap = new Map(newEntities.get(viewPath) ?? new Map());
23
+ viewMap.set(key, data);
24
+ newEntities.set(viewPath, viewMap);
25
+ return { entities: newEntities };
26
+ });
27
+ },
28
+ _delete: (viewPath, key) => {
29
+ set((state) => {
30
+ const newEntities = new Map(state.entities);
31
+ const viewMap = newEntities.get(viewPath);
32
+ if (viewMap) {
33
+ const newViewMap = new Map(viewMap);
34
+ newViewMap.delete(key);
35
+ newEntities.set(viewPath, newViewMap);
36
+ }
37
+ return { entities: newEntities };
38
+ });
39
+ },
40
+ _clear: (viewPath) => {
41
+ set((state) => {
42
+ if (viewPath) {
43
+ const newEntities = new Map(state.entities);
44
+ newEntities.delete(viewPath);
45
+ return { entities: newEntities };
46
+ }
47
+ return { entities: new Map() };
48
+ });
49
+ },
50
+ _setConnectionState: (connectionState, lastError) => {
51
+ set({ connectionState, lastError });
52
+ },
53
+ _setViewConfig: (viewPath, config) => {
54
+ set((state) => {
55
+ const newConfigs = new Map(state.viewConfigs);
56
+ newConfigs.set(viewPath, config);
57
+ return { viewConfigs: newConfigs };
58
+ });
59
+ },
60
+ }));
61
+ }
62
+ get(viewPath, key) {
63
+ const viewMap = this.store.getState().entities.get(viewPath);
64
+ if (!viewMap)
65
+ return null;
66
+ const value = viewMap.get(key);
67
+ return value !== undefined ? value : null;
68
+ }
69
+ getAll(viewPath) {
70
+ const viewMap = this.store.getState().entities.get(viewPath);
71
+ if (!viewMap)
72
+ return [];
73
+ return Array.from(viewMap.values());
74
+ }
75
+ getAllSync(viewPath) {
76
+ const viewMap = this.store.getState().entities.get(viewPath);
77
+ if (!viewMap)
78
+ return undefined;
79
+ return Array.from(viewMap.values());
80
+ }
81
+ getSync(viewPath, key) {
82
+ const viewMap = this.store.getState().entities.get(viewPath);
83
+ if (!viewMap)
84
+ return undefined;
85
+ const value = viewMap.get(key);
86
+ return value !== undefined ? value : null;
87
+ }
88
+ has(viewPath, key) {
89
+ return this.store.getState().entities.get(viewPath)?.has(key) ?? false;
90
+ }
91
+ keys(viewPath) {
92
+ const viewMap = this.store.getState().entities.get(viewPath);
93
+ if (!viewMap)
94
+ return [];
95
+ return Array.from(viewMap.keys());
96
+ }
97
+ size(viewPath) {
98
+ return this.store.getState().entities.get(viewPath)?.size ?? 0;
99
+ }
100
+ set(viewPath, key, data) {
101
+ let order = this.accessOrder.get(viewPath);
102
+ if (!order) {
103
+ order = [];
104
+ this.accessOrder.set(viewPath, order);
105
+ }
106
+ const existingIdx = order.indexOf(key);
107
+ if (existingIdx !== -1) {
108
+ order.splice(existingIdx, 1);
109
+ }
110
+ order.push(key);
111
+ this.store.getState()._set(viewPath, key, data);
112
+ }
113
+ delete(viewPath, key) {
114
+ const order = this.accessOrder.get(viewPath);
115
+ if (order) {
116
+ const idx = order.indexOf(key);
117
+ if (idx !== -1) {
118
+ order.splice(idx, 1);
119
+ }
120
+ }
121
+ this.store.getState()._delete(viewPath, key);
122
+ }
123
+ clear(viewPath) {
124
+ if (viewPath) {
125
+ this.accessOrder.delete(viewPath);
126
+ }
127
+ else {
128
+ this.accessOrder.clear();
129
+ }
130
+ this.store.getState()._clear(viewPath);
131
+ }
132
+ evictOldest(viewPath) {
133
+ const order = this.accessOrder.get(viewPath);
134
+ if (!order || order.length === 0)
135
+ return undefined;
136
+ const oldest = order.shift();
137
+ if (oldest !== undefined) {
138
+ this.store.getState()._delete(viewPath, oldest);
139
+ }
140
+ return oldest;
141
+ }
142
+ onUpdate(callback) {
143
+ this.updateCallbacks.add(callback);
144
+ return () => this.updateCallbacks.delete(callback);
145
+ }
146
+ onRichUpdate(callback) {
147
+ this.richUpdateCallbacks.add(callback);
148
+ return () => this.richUpdateCallbacks.delete(callback);
149
+ }
150
+ notifyUpdate(viewPath, key, update) {
151
+ for (const callback of this.updateCallbacks) {
152
+ callback(viewPath, key, update);
153
+ }
154
+ }
155
+ notifyRichUpdate(viewPath, key, update) {
156
+ for (const callback of this.richUpdateCallbacks) {
157
+ callback(viewPath, key, update);
158
+ }
159
+ }
160
+ setConnectionState(state, error) {
161
+ this.store.getState()._setConnectionState(state, error);
162
+ }
163
+ setViewConfig(viewPath, config) {
164
+ const state = this.store.getState();
165
+ const existingConfig = state.viewConfigs.get(viewPath);
166
+ if (existingConfig)
167
+ return;
168
+ state._setViewConfig(viewPath, config);
169
+ }
170
+ getViewConfig(viewPath) {
171
+ return this.store.getState().viewConfigs.get(viewPath);
172
+ }
173
+ }
174
+
175
+ function syncClientWallets(entries, wallet) {
176
+ for (const entry of entries) {
177
+ entry.client.setWallet(wallet);
178
+ }
179
+ }
180
+ function initializeConnectedClient(client, adapter, wallet) {
181
+ client.setWallet(wallet);
182
+ client.onConnectionStateChange((state) => {
183
+ adapter.setConnectionState(state);
184
+ });
185
+ adapter.setConnectionState(client.connectionState);
186
+ }
187
+
188
+ const objectKeys = new WeakMap();
189
+ let nextObjectKey = 0;
190
+ function getObjectKey(value, prefix) {
191
+ const existing = objectKeys.get(value);
192
+ if (existing) {
193
+ return existing;
194
+ }
195
+ const key = `${prefix}-${++nextObjectKey}`;
196
+ objectKeys.set(value, key);
197
+ return key;
198
+ }
199
+ /**
200
+ * Generated programs share by their behavior fingerprint. Manual definitions
201
+ * fall back to object identity so definitions with unknown behavior never
202
+ * accidentally share a client.
203
+ */
204
+ function getProgramsKey(programs) {
205
+ const entries = Object.entries(programs)
206
+ .map(([key, definition]) => [
207
+ key,
208
+ definition.sdkDefinitionHash
209
+ ?? getObjectKey(definition, 'program'),
210
+ ])
211
+ .sort(([left], [right]) => left.localeCompare(right));
212
+ return entries.length > 0 ? JSON.stringify(entries) : 'programs-empty';
213
+ }
214
+ function createClientCacheKey(stack, options) {
215
+ if (!stack) {
216
+ return null;
217
+ }
218
+ return [
219
+ getObjectKey(stack, 'stack'),
220
+ options?.transport ?? 'ws',
221
+ options?.url ?? stack.endpoints.ws,
222
+ options?.httpUrl ?? stack.endpoints.http ?? '',
223
+ options?.transactions
224
+ ? getObjectKey(options.transactions, 'transactions')
225
+ : 'transactions-default',
226
+ options?.programs
227
+ ? getProgramsKey(options.programs)
228
+ : 'programs-none',
229
+ ].join(':');
230
+ }
231
+
232
+ function trackConnectingPromise(connecting, cacheKey, promise) {
233
+ connecting.set(cacheKey, promise);
234
+ void promise.catch(() => {
235
+ if (connecting.get(cacheKey) === promise) {
236
+ connecting.delete(cacheKey);
237
+ }
238
+ });
239
+ return promise;
240
+ }
241
+
242
+ const AreteContext = React.createContext(null);
243
+ function AreteProvider({ children, ...config }) {
244
+ const clientsRef = React.useRef(new Map());
245
+ const connectingRef = React.useRef(new Map());
246
+ const clientChangeListenersRef = React.useRef(new Set());
247
+ const latestWalletRef = React.useRef(config.wallet);
248
+ const lifecycleRef = React.useRef({ active: true, generation: 0 });
249
+ latestWalletRef.current = config.wallet;
250
+ const notifyClientChange = React.useCallback((change) => {
251
+ clientChangeListenersRef.current.forEach(cb => { cb(change); });
252
+ }, []);
253
+ const subscribeToClientChanges = React.useCallback((callback) => {
254
+ clientChangeListenersRef.current.add(callback);
255
+ return () => {
256
+ clientChangeListenersRef.current.delete(callback);
257
+ };
258
+ }, []);
259
+ const getOrCreateClient = React.useCallback(async (stack, options) => {
260
+ const cacheKey = createClientCacheKey(stack, options);
261
+ if (!cacheKey) {
262
+ throw new Error('Stack is required to create an Arete client');
263
+ }
264
+ const existing = clientsRef.current.get(cacheKey);
265
+ if (existing) {
266
+ return existing.client;
267
+ }
268
+ const connecting = connectingRef.current.get(cacheKey);
269
+ if (connecting) {
270
+ return connecting;
271
+ }
272
+ const adapter = new ZustandAdapter();
273
+ const lifecycleGeneration = lifecycleRef.current.generation;
274
+ let connectionPromise;
275
+ connectionPromise = sdk.Arete.connect(stack, {
276
+ url: options?.url,
277
+ httpUrl: options?.httpUrl,
278
+ transport: options?.transport,
279
+ transactions: options?.transactions,
280
+ programs: options?.programs,
281
+ storage: adapter,
282
+ autoConnect: config.autoConnect,
283
+ autoReconnect: config.autoReconnect,
284
+ reconnectIntervals: config.reconnectIntervals,
285
+ maxReconnectAttempts: config.maxReconnectAttempts,
286
+ maxEntriesPerView: config.maxEntriesPerView,
287
+ flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
288
+ fetch: config.fetch,
289
+ validateFrames: config.validateFrames,
290
+ onFrameValidationError: config.onFrameValidationError,
291
+ auth: config.auth,
292
+ wallet: latestWalletRef.current,
293
+ }).then((client) => {
294
+ const ownsAttempt = lifecycleRef.current.active
295
+ && lifecycleRef.current.generation === lifecycleGeneration
296
+ && connectingRef.current.get(cacheKey) === connectionPromise;
297
+ if (!ownsAttempt) {
298
+ client.disconnect();
299
+ throw new Error('Arete connection attempt was superseded');
300
+ }
301
+ initializeConnectedClient(client, adapter, latestWalletRef.current);
302
+ clientsRef.current.set(cacheKey, {
303
+ client: client,
304
+ disconnect: () => client.disconnect()
305
+ });
306
+ connectingRef.current.delete(cacheKey);
307
+ notifyClientChange({ cacheKey, status: 'connected' });
308
+ return client;
309
+ }).catch((value) => {
310
+ const error = value instanceof Error ? value : new Error(String(value));
311
+ if (connectingRef.current.get(cacheKey) === connectionPromise) {
312
+ connectingRef.current.delete(cacheKey);
313
+ if (lifecycleRef.current.active
314
+ && lifecycleRef.current.generation === lifecycleGeneration) {
315
+ notifyClientChange({ cacheKey, status: 'error', error });
316
+ }
317
+ }
318
+ throw error;
319
+ });
320
+ trackConnectingPromise(connectingRef.current, cacheKey, connectionPromise);
321
+ notifyClientChange({ cacheKey, status: 'connecting' });
322
+ return connectionPromise;
323
+ }, [config.autoConnect, config.autoReconnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView, config.flushIntervalMs, config.fetch, config.validateFrames, config.onFrameValidationError, config.auth, notifyClientChange]);
324
+ const getClient = React.useCallback((stack, options) => {
325
+ if (!stack) {
326
+ if (clientsRef.current.size === 1) {
327
+ const firstEntry = clientsRef.current.values().next().value;
328
+ return firstEntry ? firstEntry.client : null;
329
+ }
330
+ return null;
331
+ }
332
+ const cacheKey = createClientCacheKey(stack, options);
333
+ if (!cacheKey) {
334
+ return null;
335
+ }
336
+ const entry = clientsRef.current.get(cacheKey);
337
+ return entry ? entry.client : null;
338
+ }, []);
339
+ const retryClient = React.useCallback((stack, options) => {
340
+ const cacheKey = createClientCacheKey(stack, options);
341
+ if (!cacheKey) {
342
+ return Promise.reject(new Error('Stack is required to retry an Arete client'));
343
+ }
344
+ const existing = clientsRef.current.get(cacheKey);
345
+ if (existing) {
346
+ existing.disconnect();
347
+ clientsRef.current.delete(cacheKey);
348
+ }
349
+ return getOrCreateClient(stack, options);
350
+ }, [getOrCreateClient]);
351
+ React.useEffect(() => {
352
+ syncClientWallets(clientsRef.current.values(), config.wallet);
353
+ notifyClientChange();
354
+ }, [config.wallet, notifyClientChange]);
355
+ React.useEffect(() => {
356
+ const lifecycle = lifecycleRef.current;
357
+ const clients = clientsRef.current;
358
+ const connecting = connectingRef.current;
359
+ lifecycle.active = true;
360
+ return () => {
361
+ lifecycle.active = false;
362
+ lifecycle.generation++;
363
+ clients.forEach((entry) => {
364
+ entry.disconnect();
365
+ });
366
+ clients.clear();
367
+ connecting.clear();
368
+ };
369
+ }, []);
370
+ const value = {
371
+ getOrCreateClient,
372
+ getClient,
373
+ retryClient,
374
+ subscribeToClientChanges,
375
+ config,
376
+ };
377
+ return (React.createElement(AreteContext.Provider, { value: value }, children));
378
+ }
379
+ function useAreteContext() {
380
+ const context = React.useContext(AreteContext);
381
+ if (!context) {
382
+ throw new Error('useAreteContext must be used within AreteProvider');
383
+ }
384
+ return context;
385
+ }
386
+ function useConnectionState(stack, options) {
387
+ const { getClient, subscribeToClientChanges } = useAreteContext();
388
+ const [state, setState] = React.useState(() => {
389
+ const client = getClient(stack, options);
390
+ return client?.connectionState ?? 'disconnected';
391
+ });
392
+ const unsubscribeRef = React.useRef(undefined);
393
+ React.useEffect(() => {
394
+ let mounted = true;
395
+ const setupClientSubscription = () => {
396
+ unsubscribeRef.current?.();
397
+ unsubscribeRef.current = undefined;
398
+ const client = getClient(stack, options);
399
+ if (client && mounted) {
400
+ setState(client.connectionState);
401
+ unsubscribeRef.current = client.onConnectionStateChange((newState) => {
402
+ if (mounted)
403
+ setState(newState);
404
+ });
405
+ }
406
+ else if (mounted) {
407
+ setState('disconnected');
408
+ }
409
+ };
410
+ const unsubscribeFromClientChanges = subscribeToClientChanges(setupClientSubscription);
411
+ setupClientSubscription();
412
+ return () => {
413
+ mounted = false;
414
+ unsubscribeFromClientChanges();
415
+ unsubscribeRef.current?.();
416
+ };
417
+ }, [getClient, subscribeToClientChanges, stack, options]);
418
+ return state;
419
+ }
420
+
421
+ function isMarkedDefaultReconciliation(value) {
422
+ return (typeof value === 'function'
423
+ && value.areteDefaultReconciliation === true
424
+ && typeof value.shouldReconcile === 'function'
425
+ && typeof value.withOverrides === 'function');
426
+ }
427
+ function initialMutationState() {
428
+ return {
429
+ status: 'idle',
430
+ phase: 'idle',
431
+ latestEvent: null,
432
+ displayError: null,
433
+ failure: null,
434
+ outcome: null,
435
+ prepared: null,
436
+ result: undefined,
437
+ signatures: [],
438
+ completedReceipts: [],
439
+ callbackErrors: [],
440
+ reconciliationError: null,
441
+ };
442
+ }
443
+ function normalizeThrown(value) {
444
+ if (value instanceof Error) {
445
+ return value;
446
+ }
447
+ let message;
448
+ try {
449
+ message = typeof value === 'string' ? value : JSON.stringify(value);
450
+ }
451
+ catch {
452
+ message = String(value);
453
+ }
454
+ const error = new Error(message || String(value));
455
+ error.cause = value;
456
+ return error;
457
+ }
458
+ function displayErrorFor(error, failure) {
459
+ const unwrapped = sdk.unwrapOperationExecutionError(error);
460
+ if (unwrapped instanceof sdk.InstructionError && unwrapped.programError) {
461
+ return `${unwrapped.programError.name}: ${unwrapped.programError.message}`;
462
+ }
463
+ if (failure?.status === 'chain-failed' && failure.programError) {
464
+ return `${failure.programError.name}: ${failure.programError.message}`;
465
+ }
466
+ if (failure
467
+ && failure.cause instanceof Error
468
+ && failure.cause.message) {
469
+ return failure.cause.message;
470
+ }
471
+ return unwrapped instanceof Error ? unwrapped.message : error.message;
472
+ }
473
+ function isReceipt(value) {
474
+ if (!value || typeof value !== 'object') {
475
+ return false;
476
+ }
477
+ const receipt = value;
478
+ return (typeof receipt.transactionIndex === 'number'
479
+ && typeof receipt.transactionName === 'string'
480
+ && typeof receipt.signature === 'string');
481
+ }
482
+ function receiptsFrom(value) {
483
+ if (!value || typeof value !== 'object') {
484
+ return [];
485
+ }
486
+ const candidate = value;
487
+ if (Array.isArray(candidate.completedReceipts)) {
488
+ return candidate.completedReceipts.filter(isReceipt);
489
+ }
490
+ if (Array.isArray(candidate.transactions)) {
491
+ return candidate.transactions.filter(isReceipt);
492
+ }
493
+ return isReceipt(candidate.transaction) ? [candidate.transaction] : [];
494
+ }
495
+ function signaturesFrom(value) {
496
+ if (!value || typeof value !== 'object') {
497
+ return [];
498
+ }
499
+ const candidate = value;
500
+ if (Array.isArray(candidate.signatures)) {
501
+ return candidate.signatures.filter((signature) => typeof signature === 'string');
502
+ }
503
+ return typeof candidate.signature === 'string' ? [candidate.signature] : [];
504
+ }
505
+ function callbackErrorsFrom(value) {
506
+ if (!value || typeof value !== 'object') {
507
+ return [];
508
+ }
509
+ const callbackErrors = value.callbackErrors;
510
+ return Array.isArray(callbackErrors) ? callbackErrors.map(normalizeThrown) : [];
511
+ }
512
+ function uniqueErrors(errors) {
513
+ return [...new Set(errors)];
514
+ }
515
+ function uniqueStrings(values) {
516
+ return [...new Set(values)];
517
+ }
518
+ function confirmedOutcome(signatures, receipts) {
519
+ const signature = signatures[signatures.length - 1];
520
+ if (!signature) {
521
+ return null;
522
+ }
523
+ const receipt = [...receipts].reverse().find((entry) => entry.signature === signature);
524
+ return {
525
+ status: 'confirmed',
526
+ phase: 'confirmation',
527
+ signature,
528
+ slot: receipt?.slot,
529
+ };
530
+ }
531
+ function unreconciledError(value) {
532
+ if (!value || typeof value !== 'object') {
533
+ return null;
534
+ }
535
+ const result = value;
536
+ if (result.status !== 'confirmed-unreconciled') {
537
+ return null;
538
+ }
539
+ return normalizeThrown(result.error ?? new Error('Confirmed transaction was not reconciled'));
540
+ }
541
+ function useInstructionMutation(execute, defaults) {
542
+ const [state, setState] = React.useState((initialMutationState));
543
+ const invocationRef = React.useRef(0);
544
+ const reconciliationAbortRef = React.useRef(null);
545
+ const reconciliationRetryRef = React.useRef(null);
546
+ const defaultsRef = React.useRef(defaults);
547
+ defaultsRef.current = defaults;
548
+ const submit = React.useCallback(async (args, options) => {
549
+ const invocation = ++invocationRef.current;
550
+ reconciliationAbortRef.current?.abort();
551
+ reconciliationRetryRef.current = null;
552
+ const reconciliationAbort = new AbortController();
553
+ reconciliationAbortRef.current = reconciliationAbort;
554
+ const updateCurrent = (update) => {
555
+ if (invocation === invocationRef.current) {
556
+ setState(update);
557
+ }
558
+ };
559
+ const setPhase = (phase, patch = {}, transactionEvent) => {
560
+ updateCurrent((current) => ({
561
+ ...current,
562
+ ...patch,
563
+ phase,
564
+ latestEvent: {
565
+ phase,
566
+ prepared: patch.prepared === undefined ? current.prepared : patch.prepared,
567
+ ...(patch.result === undefined ? {} : { result: patch.result }),
568
+ ...(patch.failure ? { failure: patch.failure } : {}),
569
+ ...(transactionEvent ? { transactionEvent } : {}),
570
+ },
571
+ }));
572
+ };
573
+ setState({
574
+ ...initialMutationState(),
575
+ status: 'pending',
576
+ phase: 'preparing',
577
+ latestEvent: { phase: 'preparing', prepared: null },
578
+ });
579
+ const mergedOptions = {
580
+ ...(defaultsRef.current ?? {}),
581
+ ...(options ?? {}),
582
+ };
583
+ const onConfirmed = mergedOptions.onConfirmed;
584
+ const onSuccess = mergedOptions.onSuccess;
585
+ const onError = mergedOptions.onError;
586
+ const reconcileOption = mergedOptions.reconcile;
587
+ const executionOptions = { ...mergedOptions };
588
+ delete executionOptions.onConfirmed;
589
+ delete executionOptions.onSuccess;
590
+ delete executionOptions.onError;
591
+ delete executionOptions.reconcile;
592
+ let prepared = null;
593
+ const observedReceipts = [];
594
+ const observedCallbackErrors = [];
595
+ const observer = {
596
+ onPrepared(nextPrepared) {
597
+ prepared = nextPrepared;
598
+ setPhase('awaiting-wallet', { prepared: nextPrepared });
599
+ },
600
+ onAwaitingWallet() {
601
+ setPhase('awaiting-wallet');
602
+ },
603
+ onTransactionStart(event) {
604
+ setPhase('awaiting-wallet', {}, event);
605
+ },
606
+ onTransactionSuccess(event) {
607
+ observedReceipts.push(event.receipt);
608
+ const receipts = [...observedReceipts];
609
+ const signatures = uniqueStrings(receipts.map((receipt) => receipt.signature));
610
+ setPhase('submitted', {
611
+ completedReceipts: receipts,
612
+ signatures,
613
+ }, event);
614
+ },
615
+ onCallbackError(value) {
616
+ const error = normalizeThrown(value);
617
+ observedCallbackErrors.push(error);
618
+ updateCurrent((current) => ({
619
+ ...current,
620
+ callbackErrors: uniqueErrors([...current.callbackErrors, error]),
621
+ }));
622
+ },
623
+ };
624
+ try {
625
+ let reconcile;
626
+ if (reconcileOption === false || reconcileOption === undefined) {
627
+ reconcile = undefined;
628
+ }
629
+ else if (typeof reconcileOption === 'function') {
630
+ reconcile = reconcileOption;
631
+ }
632
+ else {
633
+ const hookDefault = defaultsRef.current?.reconcile;
634
+ if (isMarkedDefaultReconciliation(hookDefault)) {
635
+ reconcile = hookDefault.withOverrides(reconcileOption);
636
+ }
637
+ else {
638
+ throw new Error('reconcile option objects require a generated program hook with default reconciliation; pass a reconcile function instead');
639
+ }
640
+ }
641
+ const result = await execute(args, executionOptions, observer);
642
+ const completedReceipts = receiptsFrom(result).length > 0
643
+ ? receiptsFrom(result)
644
+ : [...observedReceipts];
645
+ const signatures = uniqueStrings([
646
+ ...signaturesFrom(result),
647
+ ...completedReceipts.map((receipt) => receipt.signature),
648
+ ]);
649
+ const outcome = confirmedOutcome(signatures, completedReceipts);
650
+ const callbackErrors = uniqueErrors([
651
+ ...observedCallbackErrors,
652
+ ...callbackErrorsFrom(result),
653
+ ]);
654
+ const reconciliationContext = {
655
+ result,
656
+ prepared,
657
+ signatures,
658
+ completedReceipts,
659
+ signal: reconciliationAbort.signal,
660
+ };
661
+ const shouldReconcile = Boolean(reconcile
662
+ && (!isMarkedDefaultReconciliation(reconcile)
663
+ || reconcile.shouldReconcile(reconciliationContext)));
664
+ if (invocation === invocationRef.current && reconcile && shouldReconcile) {
665
+ reconciliationRetryRef.current = {
666
+ invocation,
667
+ reconcile,
668
+ context: {
669
+ result,
670
+ prepared,
671
+ signatures,
672
+ completedReceipts,
673
+ },
674
+ onSuccess,
675
+ };
676
+ }
677
+ setPhase('confirmed', {
678
+ status: 'pending',
679
+ result,
680
+ outcome,
681
+ prepared,
682
+ signatures,
683
+ completedReceipts,
684
+ callbackErrors,
685
+ });
686
+ if (onConfirmed) {
687
+ try {
688
+ await onConfirmed(result);
689
+ }
690
+ catch (value) {
691
+ const callbackError = normalizeThrown(value);
692
+ observedCallbackErrors.push(callbackError);
693
+ updateCurrent((current) => ({
694
+ ...current,
695
+ callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
696
+ }));
697
+ }
698
+ }
699
+ if (reconciliationAbort.signal.aborted || invocation !== invocationRef.current) {
700
+ return result;
701
+ }
702
+ let reconciled = !shouldReconcile;
703
+ if (reconcile && shouldReconcile) {
704
+ setPhase('reconciling', { status: 'pending' });
705
+ try {
706
+ const reconciliationResult = await reconcile(reconciliationContext);
707
+ const error = unreconciledError(reconciliationResult);
708
+ if (error) {
709
+ setPhase('confirmed-unreconciled', {
710
+ status: 'pending',
711
+ reconciliationError: error,
712
+ });
713
+ }
714
+ else {
715
+ reconciled = true;
716
+ if (reconciliationRetryRef.current?.invocation === invocation) {
717
+ reconciliationRetryRef.current = null;
718
+ }
719
+ setPhase('reconciled', { status: 'pending' });
720
+ }
721
+ }
722
+ catch (value) {
723
+ const reconciliationError = normalizeThrown(value);
724
+ setPhase('confirmed-unreconciled', {
725
+ status: 'pending',
726
+ reconciliationError,
727
+ });
728
+ }
729
+ }
730
+ if (reconciled
731
+ && onSuccess
732
+ && !reconciliationAbort.signal.aborted
733
+ && invocation === invocationRef.current) {
734
+ try {
735
+ await onSuccess(result);
736
+ }
737
+ catch (value) {
738
+ const callbackError = normalizeThrown(value);
739
+ observedCallbackErrors.push(callbackError);
740
+ updateCurrent((current) => ({
741
+ ...current,
742
+ callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
743
+ }));
744
+ }
745
+ }
746
+ updateCurrent((current) => ({ ...current, status: 'success' }));
747
+ return result;
748
+ }
749
+ catch (value) {
750
+ const error = normalizeThrown(value);
751
+ const failure = sdk.getTransactionFailureOutcome(error) ?? {
752
+ status: 'not-submitted',
753
+ phase: prepared ? 'send' : 'build',
754
+ cause: value,
755
+ };
756
+ const completedReceipts = value instanceof sdk.OperationExecutionError
757
+ ? [...value.completedReceipts]
758
+ : receiptsFrom(value);
759
+ const signatures = uniqueStrings([
760
+ ...completedReceipts.map((receipt) => receipt.signature),
761
+ ...(failure && 'signature' in failure && failure.signature ? [failure.signature] : []),
762
+ ]);
763
+ const callbackErrors = uniqueErrors([
764
+ ...observedCallbackErrors,
765
+ ...callbackErrorsFrom(value),
766
+ ]);
767
+ const phase = failure?.status ?? 'not-submitted';
768
+ const displayError = displayErrorFor(error, failure);
769
+ setPhase(phase, {
770
+ status: 'pending',
771
+ displayError,
772
+ failure,
773
+ outcome: failure,
774
+ prepared: value instanceof sdk.OperationExecutionError
775
+ ? value.operation
776
+ : prepared,
777
+ signatures,
778
+ completedReceipts,
779
+ callbackErrors,
780
+ });
781
+ if (onError) {
782
+ try {
783
+ await onError(error);
784
+ }
785
+ catch (callbackValue) {
786
+ const callbackError = normalizeThrown(callbackValue);
787
+ updateCurrent((current) => ({
788
+ ...current,
789
+ callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
790
+ }));
791
+ }
792
+ }
793
+ updateCurrent((current) => ({ ...current, status: 'error' }));
794
+ throw error;
795
+ }
796
+ finally {
797
+ if (reconciliationAbortRef.current === reconciliationAbort) {
798
+ reconciliationAbortRef.current = null;
799
+ }
800
+ }
801
+ }, [execute]);
802
+ const retryReconciliation = React.useCallback(async () => {
803
+ const pending = reconciliationRetryRef.current;
804
+ if (!pending) {
805
+ throw new Error('No failed reconciliation is available to retry');
806
+ }
807
+ const invocation = ++invocationRef.current;
808
+ reconciliationAbortRef.current?.abort();
809
+ const reconciliationAbort = new AbortController();
810
+ reconciliationAbortRef.current = reconciliationAbort;
811
+ setState((current) => ({
812
+ ...current,
813
+ status: 'pending',
814
+ phase: 'reconciling',
815
+ reconciliationError: null,
816
+ latestEvent: {
817
+ phase: 'reconciling',
818
+ prepared: pending.context.prepared,
819
+ result: pending.context.result,
820
+ },
821
+ }));
822
+ try {
823
+ const result = await pending.reconcile({
824
+ ...pending.context,
825
+ signal: reconciliationAbort.signal,
826
+ });
827
+ const error = unreconciledError(result);
828
+ if (error)
829
+ throw error;
830
+ if (reconciliationAbort.signal.aborted || invocation !== invocationRef.current)
831
+ return;
832
+ reconciliationRetryRef.current = null;
833
+ setState((current) => ({
834
+ ...current,
835
+ status: 'pending',
836
+ phase: 'reconciled',
837
+ reconciliationError: null,
838
+ latestEvent: {
839
+ phase: 'reconciled',
840
+ prepared: pending.context.prepared,
841
+ result: pending.context.result,
842
+ },
843
+ }));
844
+ if (pending.onSuccess) {
845
+ try {
846
+ await pending.onSuccess(pending.context.result);
847
+ }
848
+ catch (value) {
849
+ const callbackError = normalizeThrown(value);
850
+ if (invocation === invocationRef.current) {
851
+ setState((current) => ({
852
+ ...current,
853
+ callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
854
+ }));
855
+ }
856
+ }
857
+ }
858
+ if (invocation === invocationRef.current) {
859
+ setState((current) => ({ ...current, status: 'success' }));
860
+ }
861
+ }
862
+ catch (value) {
863
+ const error = normalizeThrown(value);
864
+ if (!reconciliationAbort.signal.aborted && invocation === invocationRef.current) {
865
+ setState((current) => ({
866
+ ...current,
867
+ status: 'success',
868
+ phase: 'confirmed-unreconciled',
869
+ reconciliationError: error,
870
+ latestEvent: {
871
+ phase: 'confirmed-unreconciled',
872
+ prepared: pending.context.prepared,
873
+ result: pending.context.result,
874
+ error,
875
+ },
876
+ }));
877
+ }
878
+ throw error;
879
+ }
880
+ finally {
881
+ if (reconciliationAbortRef.current === reconciliationAbort) {
882
+ reconciliationAbortRef.current = null;
883
+ }
884
+ }
885
+ }, []);
886
+ const reset = React.useCallback(() => {
887
+ invocationRef.current += 1;
888
+ reconciliationAbortRef.current?.abort();
889
+ reconciliationAbortRef.current = null;
890
+ reconciliationRetryRef.current = null;
891
+ setState(initialMutationState());
892
+ }, []);
893
+ const mutate = React.useCallback((args, options) => {
894
+ void submit(args, options).catch(() => undefined);
895
+ }, [submit]);
896
+ const signature = state.signatures.length === 1 ? state.signatures[0] : null;
897
+ return {
898
+ mutate,
899
+ mutateAsync: submit,
900
+ submit,
901
+ status: state.status,
902
+ phase: state.phase,
903
+ latestEvent: state.latestEvent,
904
+ error: state.displayError,
905
+ displayError: state.displayError,
906
+ failure: state.failure,
907
+ outcome: state.outcome,
908
+ prepared: state.prepared,
909
+ data: state.result,
910
+ result: state.result,
911
+ signatures: state.signatures,
912
+ signature,
913
+ completedReceipts: state.completedReceipts,
914
+ callbackError: state.callbackErrors[state.callbackErrors.length - 1] ?? null,
915
+ callbackErrors: state.callbackErrors,
916
+ reconciliationError: state.reconciliationError,
917
+ isLoading: state.status === 'pending',
918
+ isConfirmed: state.outcome?.status === 'confirmed',
919
+ isSubmittedUnknown: state.failure?.status === 'submitted-unknown',
920
+ isPreparing: state.phase === 'preparing',
921
+ isAwaitingWallet: state.phase === 'awaiting-wallet',
922
+ isReconciling: state.phase === 'reconciling',
923
+ canRetryReconciliation: state.phase === 'confirmed-unreconciled'
924
+ && reconciliationRetryRef.current !== null,
925
+ retryReconciliation,
926
+ reset,
927
+ };
928
+ }
929
+
930
+ const DEFAULT_RECONCILIATION_TIMEOUT_MS = 30000;
931
+ function toSlot(slot) {
932
+ if (typeof slot === 'number') {
933
+ if (!Number.isSafeInteger(slot) || slot < 0) {
934
+ throw new RangeError('slot must be a non-negative safe integer or bigint');
935
+ }
936
+ return BigInt(slot);
937
+ }
938
+ if (slot < 0n) {
939
+ throw new RangeError('slot must be non-negative');
940
+ }
941
+ return slot;
942
+ }
943
+ function reconciliationError(value) {
944
+ if (value instanceof Error) {
945
+ return value;
946
+ }
947
+ const error = new Error(typeof value === 'string' ? value : String(value));
948
+ error.cause = value;
949
+ return error;
950
+ }
951
+ async function refreshTargets(targets, timeoutMs, signal) {
952
+ if (targets.length === 0)
953
+ return;
954
+ let timeout;
955
+ let onAbort;
956
+ const interrupted = new Promise((_resolve, reject) => {
957
+ timeout = setTimeout(() => {
958
+ reject(new Error(`Timed out waiting for refreshed Arete data after ${timeoutMs}ms`));
959
+ }, timeoutMs);
960
+ if (signal) {
961
+ onAbort = () => reject(reconciliationError(signal.reason ?? 'Reconciliation was cancelled'));
962
+ if (signal.aborted) {
963
+ onAbort();
964
+ }
965
+ else {
966
+ signal.addEventListener('abort', onAbort, { once: true });
967
+ }
968
+ }
969
+ });
970
+ try {
971
+ await Promise.race([
972
+ Promise.all(targets.map((target) => typeof target === 'function' ? target() : target.refresh())),
973
+ interrupted,
974
+ ]);
975
+ }
976
+ finally {
977
+ if (timeout !== undefined)
978
+ clearTimeout(timeout);
979
+ if (signal && onAbort)
980
+ signal.removeEventListener('abort', onAbort);
981
+ }
982
+ }
983
+ async function reconcileProcessedSlot(client, slot, options = {}) {
984
+ const confirmedSlot = toSlot(slot);
985
+ if (!client) {
986
+ return {
987
+ status: 'confirmed-unreconciled',
988
+ confirmedSlot,
989
+ error: new Error('Arete client is not connected'),
990
+ };
991
+ }
992
+ try {
993
+ const processedSlot = await client.waitForProcessedSlot(confirmedSlot, {
994
+ timeoutMs: options.timeoutMs ?? DEFAULT_RECONCILIATION_TIMEOUT_MS,
995
+ signal: options.signal,
996
+ });
997
+ const refreshes = Array.isArray(options.refresh)
998
+ ? options.refresh
999
+ : options.refresh
1000
+ ? [options.refresh]
1001
+ : [];
1002
+ await refreshTargets(refreshes, options.timeoutMs ?? DEFAULT_RECONCILIATION_TIMEOUT_MS, options.signal);
1003
+ return { status: 'reconciled', confirmedSlot, processedSlot };
1004
+ }
1005
+ catch (value) {
1006
+ return {
1007
+ status: 'confirmed-unreconciled',
1008
+ confirmedSlot,
1009
+ error: reconciliationError(value),
1010
+ };
1011
+ }
1012
+ }
1013
+ function useReconcileProcessedSlot(client) {
1014
+ return React.useCallback((slot, options) => reconcileProcessedSlot(client, slot, options), [client]);
1015
+ }
1016
+ /**
1017
+ * Build the default reconciliation for generated program hooks: wait until the
1018
+ * stream has processed the highest slot among completed transactions, then run
1019
+ * any requested refreshes. Mutations without a reported slot (e.g. raw
1020
+ * instruction sends) skip reconciliation silently.
1021
+ */
1022
+ function createProcessedSlotReconciliation(client, baseOverrides = {}) {
1023
+ const build = (overrides) => {
1024
+ const receiptSlot = (context) => context.completedReceipts.reduce((highest, receipt) => receipt.slot === undefined
1025
+ ? highest
1026
+ : highest === undefined
1027
+ ? receipt.slot
1028
+ : Math.max(highest, receipt.slot), undefined);
1029
+ const reconcile = async (context) => {
1030
+ const slot = receiptSlot(context);
1031
+ if (slot === undefined) {
1032
+ return undefined;
1033
+ }
1034
+ return reconcileProcessedSlot(client, slot, {
1035
+ signal: context.signal,
1036
+ timeoutMs: overrides.timeoutMs,
1037
+ refresh: overrides.refresh,
1038
+ });
1039
+ };
1040
+ return Object.assign(reconcile, {
1041
+ areteDefaultReconciliation: true,
1042
+ shouldReconcile: (context) => receiptSlot(context) !== undefined,
1043
+ withOverrides: (next) => build({ ...overrides, ...next }),
1044
+ });
1045
+ };
1046
+ return build(baseOverrides);
1047
+ }
1048
+
1049
+ function composeCallbacks(observerCallback, callerCallback) {
1050
+ if (!observerCallback && !callerCallback)
1051
+ return undefined;
1052
+ return async (value) => {
1053
+ const errors = [];
1054
+ for (const callback of [observerCallback, callerCallback]) {
1055
+ if (!callback)
1056
+ continue;
1057
+ try {
1058
+ await callback(value);
1059
+ }
1060
+ catch (error) {
1061
+ errors.push(error);
1062
+ }
1063
+ }
1064
+ if (errors.length > 0)
1065
+ throw errors[0];
1066
+ };
1067
+ }
1068
+ /**
1069
+ * Wrap the mutation factory so every generated program hook reconciles against
1070
+ * the stream by default: after confirmation, wait until the stack has
1071
+ * processed the transaction's slot. Callers opt out per hook or per submit
1072
+ * with `reconcile: false`, replace it with a function, or tweak it with a
1073
+ * `{ refresh, timeoutMs }` shorthand object.
1074
+ */
1075
+ function withDefaultReconciliation(createMutationHook, client) {
1076
+ const defaultReconcile = client && typeof client.waitForProcessedSlot === 'function'
1077
+ ? createProcessedSlotReconciliation(client)
1078
+ : undefined;
1079
+ if (!defaultReconcile) {
1080
+ return createMutationHook;
1081
+ }
1082
+ const factory = (execute, options) => {
1083
+ const callerReconcile = options?.reconcile;
1084
+ let reconcile = callerReconcile;
1085
+ if (callerReconcile !== false && typeof callerReconcile !== 'function') {
1086
+ reconcile = (callerReconcile
1087
+ ? defaultReconcile.withOverrides(callerReconcile)
1088
+ : defaultReconcile);
1089
+ }
1090
+ return createMutationHook(execute, { ...options, reconcile });
1091
+ };
1092
+ return factory;
1093
+ }
1094
+ function wrapRawInstruction(instruction, client, createMutationHook) {
1095
+ const execute = async (input, options, observer) => {
1096
+ if (!client) {
1097
+ throw new Error('Arete client is not connected');
1098
+ }
1099
+ const built = instruction.build(input);
1100
+ observer?.onAwaitingWallet();
1101
+ return client.transaction([built], options);
1102
+ };
1103
+ return {
1104
+ execute,
1105
+ build: instruction.build,
1106
+ useMutation: (options) => createMutationHook(execute, options),
1107
+ };
1108
+ }
1109
+ function isConnectedOperation(value) {
1110
+ return Boolean(value && typeof value === 'object' && typeof value.prepare === 'function');
1111
+ }
1112
+ function wrapOperation(operation, client, createMutationHook) {
1113
+ const execute = async (input, options, observer) => {
1114
+ if (!client) {
1115
+ throw new Error('Arete client is not connected');
1116
+ }
1117
+ const prepared = await operation.prepare(input);
1118
+ observer?.onPrepared(prepared);
1119
+ observer?.onAwaitingWallet();
1120
+ const executionOptions = options;
1121
+ return client.execute(prepared, {
1122
+ ...(executionOptions ?? {}),
1123
+ onTransactionStart: composeCallbacks(observer ? (event) => observer.onTransactionStart(event) : undefined, executionOptions?.onTransactionStart),
1124
+ onTransactionSuccess: composeCallbacks(observer ? (event) => observer.onTransactionSuccess(event) : undefined, executionOptions?.onTransactionSuccess),
1125
+ onCallbackError: composeCallbacks(observer ? (error) => observer.onCallbackError(error) : undefined, executionOptions?.onCallbackError),
1126
+ });
1127
+ };
1128
+ return {
1129
+ prepare: operation.prepare.bind(operation),
1130
+ execute,
1131
+ useMutation: (options) => createMutationHook(execute, options),
1132
+ };
1133
+ }
1134
+ function wrapOperationNamespace(namespace, client, createMutationHook) {
1135
+ const wrapped = {};
1136
+ for (const [name, value] of Object.entries(namespace)) {
1137
+ if (isConnectedOperation(value)) {
1138
+ wrapped[name] = wrapOperation(value, client, createMutationHook);
1139
+ continue;
1140
+ }
1141
+ if (value && typeof value === 'object') {
1142
+ wrapped[name] = wrapOperationNamespace(value, client, createMutationHook);
1143
+ }
1144
+ }
1145
+ return wrapped;
1146
+ }
1147
+ function wrapMaybeOperationNamespace(namespace, client, createMutationHook) {
1148
+ return namespace
1149
+ ? wrapOperationNamespace(namespace, client, createMutationHook)
1150
+ : {};
1151
+ }
1152
+ const NOT_CONNECTED_ERROR = 'Arete client is not connected';
1153
+ function useDisconnectedMutation() {
1154
+ return useInstructionMutation(async () => {
1155
+ throw new Error(NOT_CONNECTED_ERROR);
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Placeholder program hooks used before the client connects. Every namespace
1160
+ * path resolves (so `arete.programs.ore.transactions.mining.deploy.useMutation()`
1161
+ * is safe to call unconditionally) and every mutation hook keeps React state,
1162
+ * but preparing, executing, or submitting throws "not connected" — matching
1163
+ * what the real wrappers do. This lets components render their full tree
1164
+ * while the stack is offline instead of gating on `arete.client`.
1165
+ */
1166
+ function buildDisconnectedProgramHooks() {
1167
+ const notConnected = () => {
1168
+ throw new Error(NOT_CONNECTED_ERROR);
1169
+ };
1170
+ const leaf = Object.assign(notConnected, {
1171
+ prepare: notConnected,
1172
+ execute: notConnected,
1173
+ send: notConnected,
1174
+ resolve: notConnected,
1175
+ plan: notConnected,
1176
+ build: notConnected,
1177
+ stage: notConnected,
1178
+ useMutation: () => useDisconnectedMutation(),
1179
+ });
1180
+ // eslint-disable-next-line prefer-const
1181
+ let proxy;
1182
+ proxy = new Proxy(leaf, {
1183
+ get(target, property) {
1184
+ if (typeof property === 'string' && !(property in target)) {
1185
+ return proxy;
1186
+ }
1187
+ return Reflect.get(target, property);
1188
+ },
1189
+ });
1190
+ return proxy;
1191
+ }
1192
+ function buildProgramHookInterfaces(programs, client, createMutationHook, options = {}) {
1193
+ const wrappedPrograms = {};
1194
+ if (!programs) {
1195
+ return wrappedPrograms;
1196
+ }
1197
+ const mutationHookFactory = options.defaultReconciliation === false
1198
+ ? createMutationHook
1199
+ : withDefaultReconciliation(createMutationHook, client);
1200
+ for (const [name, program] of Object.entries(programs)) {
1201
+ const raw = {};
1202
+ for (const [instructionName, instruction] of Object.entries(program.raw)) {
1203
+ raw[instructionName] = wrapRawInstruction(instruction, client, mutationHookFactory);
1204
+ }
1205
+ wrappedPrograms[name] = {
1206
+ name: program.name,
1207
+ programId: program.programId,
1208
+ schemas: program.schemas,
1209
+ pdas: program.pdas,
1210
+ accounts: program.accounts,
1211
+ queries: program.queries,
1212
+ addresses: program.addresses,
1213
+ constants: program.constants,
1214
+ defaults: program.defaults,
1215
+ math: program.math,
1216
+ raw: raw,
1217
+ instructions: wrapMaybeOperationNamespace(program.instructions, client, mutationHookFactory),
1218
+ transactions: wrapMaybeOperationNamespace(program.transactions, client, mutationHookFactory),
1219
+ flows: wrapMaybeOperationNamespace(program.flows, client, mutationHookFactory),
1220
+ };
1221
+ }
1222
+ return wrappedPrograms;
1223
+ }
1224
+
1225
+ function stableKeyPart(value) {
1226
+ if (value === undefined)
1227
+ return 'undefined';
1228
+ if (value === null)
1229
+ return 'null';
1230
+ if (typeof value === 'bigint')
1231
+ return `bigint:${value}`;
1232
+ if (typeof value === 'string')
1233
+ return `string:${JSON.stringify(value)}`;
1234
+ if (typeof value === 'number')
1235
+ return `number:${String(value)}`;
1236
+ if (typeof value === 'boolean')
1237
+ return `boolean:${String(value)}`;
1238
+ if (Array.isArray(value)) {
1239
+ return `[${value.map(stableKeyPart).join(',')}]`;
1240
+ }
1241
+ const record = value;
1242
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableKeyPart(record[key])}`).join(',')}}`;
1243
+ }
1244
+ function normalizeReadError(value) {
1245
+ if (value instanceof Error) {
1246
+ return value;
1247
+ }
1248
+ const error = new Error(typeof value === 'string' ? value : String(value));
1249
+ error.cause = value;
1250
+ return error;
1251
+ }
1252
+ /**
1253
+ * Minimal keyed async state with no cache, retry, or request deduplication.
1254
+ * A newer request aborts and supersedes the previous request for this hook.
1255
+ *
1256
+ * The returned state is tagged with the key that produced it: when the key
1257
+ * changes, the hook immediately reports the fresh key's initial state instead
1258
+ * of briefly exposing data fetched for the previous key. Callers never need
1259
+ * to re-verify that `data` matches the arguments they passed.
1260
+ */
1261
+ function useAsyncRead(key, read, options = {}) {
1262
+ const stableKey = stableKeyPart(key);
1263
+ const enabled = options.enabled ?? true;
1264
+ const [state, setState] = React.useState(() => ({
1265
+ key: stableKey,
1266
+ enabled,
1267
+ data: options.initialData,
1268
+ hasResolved: options.initialData !== undefined,
1269
+ error: null,
1270
+ isLoading: enabled && options.initialData === undefined,
1271
+ isRefreshing: false,
1272
+ }));
1273
+ const readRef = React.useRef(read);
1274
+ const keyRef = React.useRef(key);
1275
+ const enabledRef = React.useRef(enabled);
1276
+ const initialDataRef = React.useRef(options.initialData);
1277
+ const dataRef = React.useRef(options.initialData);
1278
+ const hasResolvedRef = React.useRef(options.initialData !== undefined);
1279
+ const requestRef = React.useRef({
1280
+ id: 0,
1281
+ controller: null,
1282
+ });
1283
+ const debounceTimerRef = React.useRef(null);
1284
+ const mountedRef = React.useRef(true);
1285
+ readRef.current = read;
1286
+ keyRef.current = key;
1287
+ enabledRef.current = enabled;
1288
+ initialDataRef.current = options.initialData;
1289
+ dataRef.current = state.data;
1290
+ const cancelCurrent = React.useCallback(() => {
1291
+ if (debounceTimerRef.current !== null) {
1292
+ clearTimeout(debounceTimerRef.current);
1293
+ debounceTimerRef.current = null;
1294
+ }
1295
+ requestRef.current.id += 1;
1296
+ requestRef.current.controller?.abort();
1297
+ requestRef.current.controller = null;
1298
+ }, []);
1299
+ const runRead = React.useCallback(async () => {
1300
+ if (debounceTimerRef.current !== null) {
1301
+ clearTimeout(debounceTimerRef.current);
1302
+ debounceTimerRef.current = null;
1303
+ }
1304
+ if (!enabledRef.current) {
1305
+ return dataRef.current;
1306
+ }
1307
+ const id = requestRef.current.id + 1;
1308
+ requestRef.current.id = id;
1309
+ requestRef.current.controller?.abort();
1310
+ const controller = new AbortController();
1311
+ requestRef.current.controller = controller;
1312
+ const hasData = hasResolvedRef.current;
1313
+ if (mountedRef.current) {
1314
+ setState((current) => ({
1315
+ ...current,
1316
+ error: null,
1317
+ isLoading: !hasData,
1318
+ isRefreshing: hasData,
1319
+ }));
1320
+ }
1321
+ try {
1322
+ const data = await readRef.current({
1323
+ signal: controller.signal,
1324
+ key: keyRef.current,
1325
+ });
1326
+ if (mountedRef.current && requestRef.current.id === id) {
1327
+ dataRef.current = data;
1328
+ hasResolvedRef.current = true;
1329
+ setState({
1330
+ key: stableKeyPart(keyRef.current),
1331
+ enabled: true,
1332
+ data,
1333
+ hasResolved: true,
1334
+ error: null,
1335
+ isLoading: false,
1336
+ isRefreshing: false,
1337
+ });
1338
+ }
1339
+ return data;
1340
+ }
1341
+ catch (value) {
1342
+ const error = normalizeReadError(value);
1343
+ if (mountedRef.current
1344
+ && requestRef.current.id === id
1345
+ && !controller.signal.aborted) {
1346
+ setState((current) => ({
1347
+ ...current,
1348
+ error,
1349
+ isLoading: false,
1350
+ isRefreshing: false,
1351
+ }));
1352
+ }
1353
+ throw error;
1354
+ }
1355
+ finally {
1356
+ if (requestRef.current.id === id) {
1357
+ requestRef.current.controller = null;
1358
+ }
1359
+ }
1360
+ }, []);
1361
+ React.useEffect(() => {
1362
+ mountedRef.current = true;
1363
+ return () => {
1364
+ mountedRef.current = false;
1365
+ };
1366
+ }, [cancelCurrent]);
1367
+ React.useEffect(() => {
1368
+ cancelCurrent();
1369
+ dataRef.current = initialDataRef.current;
1370
+ hasResolvedRef.current = initialDataRef.current !== undefined;
1371
+ setState({
1372
+ key: stableKey,
1373
+ enabled: enabledRef.current,
1374
+ data: initialDataRef.current,
1375
+ hasResolved: initialDataRef.current !== undefined,
1376
+ error: null,
1377
+ isLoading: enabledRef.current && initialDataRef.current === undefined,
1378
+ isRefreshing: false,
1379
+ });
1380
+ if (enabledRef.current) {
1381
+ const debounceMs = options.debounceMs ?? 0;
1382
+ if (debounceMs > 0) {
1383
+ debounceTimerRef.current = setTimeout(() => {
1384
+ debounceTimerRef.current = null;
1385
+ void runRead().catch(() => undefined);
1386
+ }, debounceMs);
1387
+ }
1388
+ else {
1389
+ void runRead().catch(() => undefined);
1390
+ }
1391
+ }
1392
+ return cancelCurrent;
1393
+ }, [cancelCurrent, runRead, stableKey, enabled, options.debounceMs]);
1394
+ // State committed under a different key must not leak into this render:
1395
+ // report the fresh key's initial state until the effect above commits.
1396
+ const visible = state.key === stableKey && state.enabled === enabled
1397
+ ? state
1398
+ : {
1399
+ key: stableKey,
1400
+ enabled,
1401
+ data: options.initialData,
1402
+ hasResolved: options.initialData !== undefined,
1403
+ error: null,
1404
+ isLoading: enabled && options.initialData === undefined,
1405
+ isRefreshing: false,
1406
+ };
1407
+ const status = !enabled
1408
+ ? options.disabledStatus ?? 'disabled'
1409
+ : visible.error
1410
+ ? 'error'
1411
+ : visible.isLoading
1412
+ ? 'loading'
1413
+ : visible.isRefreshing
1414
+ ? 'refreshing'
1415
+ : 'ready';
1416
+ return {
1417
+ data: visible.data,
1418
+ error: visible.error,
1419
+ status,
1420
+ isPending: status === 'connecting' || status === 'loading',
1421
+ isReady: status === 'ready' || status === 'refreshing',
1422
+ isEmpty: visible.hasResolved
1423
+ && (status === 'ready' || status === 'refreshing')
1424
+ && visible.data == null,
1425
+ isLoading: visible.isLoading,
1426
+ isRefreshing: visible.isRefreshing,
1427
+ refresh: runRead,
1428
+ };
1429
+ }
1430
+
1431
+ const readSourceIds = new WeakMap();
1432
+ let nextReadSourceId = 0;
1433
+ function readSourceId(read) {
1434
+ if (!read)
1435
+ return 0;
1436
+ const existing = readSourceIds.get(read);
1437
+ if (existing !== undefined)
1438
+ return existing;
1439
+ const id = ++nextReadSourceId;
1440
+ readSourceIds.set(read, id);
1441
+ return id;
1442
+ }
1443
+ /**
1444
+ * Wrap a stack's connected `read` functions in React hooks, mirroring how
1445
+ * program operations get `useMutation`. Each read exposes `.use(...args)`:
1446
+ * the args form the cache key and the read stays disabled while any required
1447
+ * arg is nullish, so `read.solClaimPreview.use(authority)` simply waits for a
1448
+ * connected wallet instead of needing non-null assertions and an `enabled`
1449
+ * flag.
1450
+ *
1451
+ * Hook-only options occupy the position after every declared read argument.
1452
+ * Optional read arguments must therefore be passed as `undefined` when options
1453
+ * follow them, keeping object-valued read arguments unambiguous.
1454
+ *
1455
+ * A proxy backs the interface so hooks exist (and stay disabled) before the
1456
+ * client connects — components can call them unconditionally.
1457
+ */
1458
+ function buildReadInterfaces(read, readArgCounts) {
1459
+ return new Proxy({}, {
1460
+ get: (_target, name) => {
1461
+ if (typeof name !== 'string') {
1462
+ return undefined;
1463
+ }
1464
+ const readFn = read?.[name];
1465
+ const sourceId = readSourceId(readFn);
1466
+ const readArgCount = readArgCounts?.[name];
1467
+ const [requiredArgCount, totalArgCount] = Array.isArray(readArgCount)
1468
+ ? readArgCount
1469
+ : [readArgCount, readArgCount];
1470
+ const useRead = (options, args) => {
1471
+ const hasArgs = args.length >= requiredArgCount
1472
+ && args
1473
+ .slice(0, requiredArgCount)
1474
+ .every((arg) => arg !== undefined && arg !== null);
1475
+ return useAsyncRead([name, sourceId, ...args], async () => {
1476
+ if (!readFn) {
1477
+ throw new Error('Arete client is not connected');
1478
+ }
1479
+ return readFn(...args);
1480
+ }, {
1481
+ ...options,
1482
+ enabled: readFn != null && hasArgs,
1483
+ disabledStatus: hasArgs && readFn == null ? 'connecting' : 'disabled',
1484
+ });
1485
+ };
1486
+ const use = (...argsAndOptions) => {
1487
+ if (!Number.isInteger(requiredArgCount)
1488
+ || !Number.isInteger(totalArgCount)
1489
+ || requiredArgCount < 0
1490
+ || totalArgCount < requiredArgCount) {
1491
+ throw new Error(`Missing read argument count metadata for "${name}"`);
1492
+ }
1493
+ let options;
1494
+ let args = argsAndOptions;
1495
+ if (argsAndOptions.length <= totalArgCount) {
1496
+ options = undefined;
1497
+ }
1498
+ else if (argsAndOptions.length !== totalArgCount + 1) {
1499
+ throw new Error(`Read hook "${name}" expects ${totalArgCount} argument(s) followed by optional hook options`);
1500
+ }
1501
+ else {
1502
+ const candidate = argsAndOptions[totalArgCount];
1503
+ if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
1504
+ throw new Error(`Read hook options for "${name}" must be an object`);
1505
+ }
1506
+ options = candidate;
1507
+ args = argsAndOptions.slice(0, totalArgCount);
1508
+ }
1509
+ return useRead(options, args);
1510
+ };
1511
+ const imperative = (...args) => {
1512
+ if (!readFn) {
1513
+ throw new Error('Arete client is not connected');
1514
+ }
1515
+ return readFn(...args);
1516
+ };
1517
+ return Object.assign(imperative, {
1518
+ use,
1519
+ });
1520
+ },
1521
+ });
1522
+ }
1523
+
1524
+ const noop = () => { };
1525
+ const developmentEnvironment = typeof process === 'undefined' || process.env?.NODE_ENV !== 'production';
1526
+ function queryError(value, fallback) {
1527
+ if (value instanceof sdk.AreteError)
1528
+ return value;
1529
+ return new sdk.AreteError(value instanceof Error ? value.message : fallback, 'SUBSCRIPTION_ERROR', value);
1530
+ }
1531
+ function useQuery(client, query, snapshotEnabled, enabled, initialData) {
1532
+ const queryKey = sdk.canonicalQueryKey({ query, snapshot: { enabled: snapshotEnabled } });
1533
+ const stateRef = React.useRef({
1534
+ client,
1535
+ queryKey,
1536
+ lease: null,
1537
+ hasInitialData: initialData !== undefined,
1538
+ fallback: {
1539
+ subscriptionId: `pending:${queryKey}`,
1540
+ query,
1541
+ keys: [],
1542
+ data: initialData ?? [],
1543
+ isLoading: enabled && snapshotEnabled && initialData === undefined,
1544
+ isRefreshing: false,
1545
+ },
1546
+ });
1547
+ if (stateRef.current.client !== client || stateRef.current.queryKey !== queryKey) {
1548
+ stateRef.current = {
1549
+ client,
1550
+ queryKey,
1551
+ lease: null,
1552
+ hasInitialData: initialData !== undefined,
1553
+ fallback: {
1554
+ subscriptionId: `pending:${queryKey}`,
1555
+ query,
1556
+ keys: [],
1557
+ data: initialData ?? [],
1558
+ isLoading: enabled && snapshotEnabled && initialData === undefined,
1559
+ isRefreshing: false,
1560
+ },
1561
+ };
1562
+ }
1563
+ const state = stateRef.current;
1564
+ const subscribe = React.useCallback((callback) => {
1565
+ if (!enabled || !client)
1566
+ return noop;
1567
+ try {
1568
+ const lease = client.getSubscriptionRegistry().subscribe(query, snapshotEnabled);
1569
+ state.lease = lease;
1570
+ const unsubscribe = lease.onChange(callback);
1571
+ return () => {
1572
+ unsubscribe();
1573
+ lease.release();
1574
+ if (state.lease === lease)
1575
+ state.lease = null;
1576
+ };
1577
+ }
1578
+ catch (value) {
1579
+ state.fallback = {
1580
+ ...state.fallback,
1581
+ isLoading: false,
1582
+ isRefreshing: false,
1583
+ error: queryError(value, 'Subscription failed'),
1584
+ };
1585
+ return noop;
1586
+ }
1587
+ // queryKey represents every query field; state is replaced with that key.
1588
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1589
+ }, [client, enabled, queryKey, snapshotEnabled]);
1590
+ const getSnapshot = React.useCallback(() => {
1591
+ if (!state.lease)
1592
+ return state.fallback;
1593
+ try {
1594
+ const current = state.lease.getSnapshot();
1595
+ if (state.hasInitialData && current.isLoading && current.data.length === 0) {
1596
+ if (state.seededSource !== current) {
1597
+ state.seededSource = current;
1598
+ state.seededSnapshot = {
1599
+ ...current,
1600
+ data: state.fallback.data,
1601
+ isLoading: false,
1602
+ };
1603
+ }
1604
+ return state.seededSnapshot;
1605
+ }
1606
+ return current;
1607
+ }
1608
+ catch (value) {
1609
+ state.fallback = {
1610
+ ...state.fallback,
1611
+ isLoading: false,
1612
+ isRefreshing: false,
1613
+ error: queryError(value, 'Subscription snapshot failed'),
1614
+ };
1615
+ return state.fallback;
1616
+ }
1617
+ // state is replaced whenever either listed identity changes.
1618
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1619
+ }, [queryKey, client]);
1620
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1621
+ const getServerSnapshot = React.useCallback(() => state.fallback, [queryKey, client]);
1622
+ const snapshot = React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1623
+ const refresh = React.useCallback(async () => {
1624
+ if (!state.lease)
1625
+ return;
1626
+ await state.lease.refresh();
1627
+ // state is replaced whenever either listed identity changes.
1628
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1629
+ }, [queryKey, client]);
1630
+ return { snapshot, refresh };
1631
+ }
1632
+ function computeStatus(enabled, client, snapshot) {
1633
+ if (!enabled)
1634
+ return 'disabled';
1635
+ if (!client)
1636
+ return 'connecting';
1637
+ if (snapshot.error)
1638
+ return 'error';
1639
+ return snapshot.isLoading ? 'subscribing' : 'ready';
1640
+ }
1641
+ function useProjectedData(snapshot, schema, onSchemaValidationError) {
1642
+ const callbackRef = React.useRef(onSchemaValidationError);
1643
+ callbackRef.current = onSchemaValidationError;
1644
+ const cache = React.useRef({ snapshot: null, schema, value: [], diagnostics: [] });
1645
+ if (cache.current.snapshot !== snapshot || cache.current.schema !== schema) {
1646
+ const value = [];
1647
+ const diagnostics = [];
1648
+ snapshot.data.forEach((entity, index) => {
1649
+ if (!schema) {
1650
+ value.push(entity);
1651
+ return;
1652
+ }
1653
+ try {
1654
+ const parsed = schema.safeParse(entity);
1655
+ if (parsed.success) {
1656
+ value.push(parsed.data);
1657
+ }
1658
+ else {
1659
+ diagnostics.push({
1660
+ view: snapshot.query.view,
1661
+ ...(snapshot.keys[index] === undefined ? {} : { key: snapshot.keys[index] }),
1662
+ entity,
1663
+ error: parsed.error,
1664
+ });
1665
+ }
1666
+ }
1667
+ catch (error) {
1668
+ diagnostics.push({
1669
+ view: snapshot.query.view,
1670
+ ...(snapshot.keys[index] === undefined ? {} : { key: snapshot.keys[index] }),
1671
+ entity,
1672
+ error,
1673
+ });
1674
+ }
1675
+ });
1676
+ cache.current = { snapshot, schema, value, diagnostics };
1677
+ }
1678
+ const projection = cache.current;
1679
+ React.useEffect(() => {
1680
+ if (projection.diagnostics.length === 0)
1681
+ return;
1682
+ const callback = callbackRef.current;
1683
+ if (callback) {
1684
+ for (const diagnostic of projection.diagnostics) {
1685
+ try {
1686
+ callback(diagnostic);
1687
+ }
1688
+ catch (error) {
1689
+ console.error('[Arete] View schema validation callback failed:', error);
1690
+ }
1691
+ }
1692
+ return;
1693
+ }
1694
+ if (!developmentEnvironment)
1695
+ return;
1696
+ const view = snapshot.query.view;
1697
+ const singleKey = snapshot.keys.length === 1 ? snapshot.keys[0] : undefined;
1698
+ const warning = {
1699
+ view,
1700
+ ...(singleKey === undefined ? {} : { key: singleKey }),
1701
+ rejectedCount: projection.diagnostics.length,
1702
+ diagnostics: projection.diagnostics,
1703
+ };
1704
+ console.warn(`[Arete] View schema filtered ${projection.diagnostics.length} entit${projection.diagnostics.length === 1 ? 'y' : 'ies'} from ${view}. Pass onSchemaValidationError to inspect or suppress this warning.`, warning);
1705
+ // The projection object changes whenever snapshot/schema changes and carries
1706
+ // the diagnostic context used here.
1707
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1708
+ }, [projection]);
1709
+ return projection.value;
1710
+ }
1711
+ function useStateView(viewDef, client, key, options) {
1712
+ const enabled = options?.enabled !== false && key !== null && key !== undefined;
1713
+ const keyString = key === null || key === undefined
1714
+ ? undefined
1715
+ : sdk.serializeViewKey(viewDef, key);
1716
+ const query = {
1717
+ view: viewDef.view,
1718
+ ...(keyString === undefined ? {} : { key: keyString }),
1719
+ ...(options?.partition === undefined ? {} : { partition: options.partition }),
1720
+ ...(options?.filters === undefined ? {} : { filters: options.filters }),
1721
+ ...(options?.after === undefined ? {} : { after: options.after }),
1722
+ ...(options?.snapshotLimit === undefined ? {} : { snapshotLimit: options.snapshotLimit }),
1723
+ };
1724
+ const snapshotEnabled = options?.withSnapshot ?? true;
1725
+ const { snapshot, refresh } = useQuery(client, query, snapshotEnabled, enabled, options?.initialData === undefined ? undefined : [options.initialData]);
1726
+ const data = useProjectedData(snapshot, options?.schema, options?.onSchemaValidationError)[0];
1727
+ const status = computeStatus(enabled, client, snapshot);
1728
+ return {
1729
+ data: enabled ? data : undefined,
1730
+ status,
1731
+ isPending: status === 'connecting' || status === 'subscribing',
1732
+ isReady: status === 'ready',
1733
+ isEmpty: status === 'ready' && data === undefined,
1734
+ isLoading: enabled && snapshot.isLoading,
1735
+ isRefreshing: enabled && snapshot.isRefreshing,
1736
+ error: status === 'error' ? snapshot.error : undefined,
1737
+ refresh,
1738
+ };
1739
+ }
1740
+ function useListView(viewDef, client, params, options) {
1741
+ const enabled = options?.enabled !== false;
1742
+ const partition = params?.partition ?? options?.partition;
1743
+ const filters = params?.filters ?? options?.filters;
1744
+ const after = params?.after ?? options?.after;
1745
+ const snapshotLimit = params?.snapshotLimit ?? options?.snapshotLimit;
1746
+ const query = {
1747
+ view: viewDef.view,
1748
+ ...(params?.key === undefined ? {} : { key: params.key }),
1749
+ ...(partition === undefined ? {} : { partition }),
1750
+ ...(filters === undefined ? {} : { filters }),
1751
+ ...(params?.take === undefined ? {} : { take: params.take }),
1752
+ ...(params?.skip === undefined ? {} : { skip: params.skip }),
1753
+ ...(after === undefined ? {} : { after }),
1754
+ ...(snapshotLimit === undefined ? {} : { snapshotLimit }),
1755
+ };
1756
+ const snapshotEnabled = params?.withSnapshot ?? options?.withSnapshot ?? true;
1757
+ const { snapshot, refresh } = useQuery(client, query, snapshotEnabled, enabled, options?.initialData);
1758
+ const data = useProjectedData(snapshot, (params?.schema ?? options?.schema), params?.onSchemaValidationError ?? options?.onSchemaValidationError);
1759
+ const status = computeStatus(enabled, client, snapshot);
1760
+ return {
1761
+ data: enabled ? data : options?.initialData,
1762
+ status,
1763
+ isPending: status === 'connecting' || status === 'subscribing',
1764
+ isReady: status === 'ready',
1765
+ isEmpty: status === 'ready' && data.length === 0,
1766
+ isLoading: enabled && snapshot.isLoading,
1767
+ isRefreshing: enabled && snapshot.isRefreshing,
1768
+ error: status === 'error' ? snapshot.error : undefined,
1769
+ refresh,
1770
+ };
1771
+ }
1772
+ function createStateViewHook(viewDef, client) {
1773
+ function use(key, options) {
1774
+ return useStateView(viewDef, client, key, options);
1775
+ }
1776
+ function refresh(key) {
1777
+ if (!client)
1778
+ return Promise.resolve();
1779
+ const keyString = key === null || key === undefined
1780
+ ? undefined
1781
+ : sdk.serializeViewKey(viewDef, key);
1782
+ return client.getSubscriptionRegistry().refreshView(viewDef.view, keyString);
1783
+ }
1784
+ return { use, refresh };
1785
+ }
1786
+ function createListViewHook(viewDef, client) {
1787
+ function use(params, options) {
1788
+ const listOptions = params?.take === 1
1789
+ ? options
1790
+ ? {
1791
+ ...options,
1792
+ initialData: options.initialData === undefined
1793
+ ? undefined
1794
+ : [options.initialData],
1795
+ }
1796
+ : undefined
1797
+ : options;
1798
+ const result = useListView(viewDef, client, params, listOptions);
1799
+ if (params?.take !== 1)
1800
+ return result;
1801
+ return { ...result, data: result.data?.[0] };
1802
+ }
1803
+ function useOne(params, options) {
1804
+ const listOptions = options
1805
+ ? {
1806
+ ...options,
1807
+ initialData: options.initialData === undefined
1808
+ ? undefined
1809
+ : [options.initialData],
1810
+ }
1811
+ : undefined;
1812
+ const result = useListView(viewDef, client, { ...(params ?? {}), take: 1 }, listOptions);
1813
+ return { ...result, data: result.data?.[0] };
1814
+ }
1815
+ function refresh() {
1816
+ if (!client)
1817
+ return Promise.resolve();
1818
+ return client.getSubscriptionRegistry().refreshView(viewDef.view);
1819
+ }
1820
+ return { use, useOne, refresh };
1821
+ }
1822
+
1823
+ function normalizeConnectionError(value, fallback) {
1824
+ if (value instanceof Error)
1825
+ return value;
1826
+ return new Error(typeof value === 'string' && value.length > 0 ? value : fallback);
1827
+ }
1828
+ function useArete(stack,
1829
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1830
+ options
1831
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1832
+ ) {
1833
+ const { getOrCreateClient, getClient, retryClient, subscribeToClientChanges, config, } = useAreteContext();
1834
+ const resolvedStack = stack ?? config.stack;
1835
+ if (!resolvedStack) {
1836
+ throw new Error('useArete() was called without a stack and no default stack is configured. ' +
1837
+ 'Pass a stack explicitly or set <AreteProvider stack={...}>.');
1838
+ }
1839
+ const usesProviderStack = stack === undefined || stack === config.stack;
1840
+ const resolvedOptions = (usesProviderStack
1841
+ ? { ...(config.stackOptions ?? {}), ...(options ?? {}) }
1842
+ : options);
1843
+ const url = resolvedOptions?.url;
1844
+ const httpUrl = resolvedOptions?.httpUrl;
1845
+ const transport = resolvedOptions?.transport;
1846
+ const transactions = resolvedOptions?.transactions;
1847
+ const attachedPrograms = resolvedOptions?.programs;
1848
+ const lookupOptions = React.useMemo(() => ({ url, httpUrl, transport, transactions, programs: attachedPrograms }), [url, httpUrl, transport, transactions, attachedPrograms]);
1849
+ const lookupKey = createClientCacheKey(resolvedStack, lookupOptions);
1850
+ const initialClient = getClient(resolvedStack, lookupOptions);
1851
+ const [clientState, setClientState] = React.useState(() => ({ lookupKey, client: initialClient }));
1852
+ const clientStateRef = React.useRef(clientState);
1853
+ clientStateRef.current = clientState;
1854
+ const client = clientState.lookupKey === lookupKey ? clientState.client : null;
1855
+ const [isConnecting, setIsConnecting] = React.useState(!initialClient);
1856
+ const [error, setError] = React.useState(null);
1857
+ const [socketIssue, setSocketIssue] = React.useState(null);
1858
+ const [connectionState, setConnectionState] = React.useState(() => client?.connectionState ?? 'disconnected');
1859
+ const retryPromiseRef = React.useRef(null);
1860
+ const lookupKeyRef = React.useRef(lookupKey);
1861
+ lookupKeyRef.current = lookupKey;
1862
+ React.useEffect(() => {
1863
+ let active = true;
1864
+ const existingClient = getClient(resolvedStack, lookupOptions);
1865
+ if (existingClient) {
1866
+ setClientState({
1867
+ lookupKey,
1868
+ client: existingClient,
1869
+ });
1870
+ setIsConnecting(false);
1871
+ setError(null);
1872
+ setSocketIssue(null);
1873
+ return () => {
1874
+ active = false;
1875
+ };
1876
+ }
1877
+ setClientState({ lookupKey, client: null });
1878
+ setIsConnecting(true);
1879
+ setError(null);
1880
+ setSocketIssue(null);
1881
+ getOrCreateClient(resolvedStack, lookupOptions)
1882
+ .then((newClient) => {
1883
+ if (active) {
1884
+ setClientState({
1885
+ lookupKey,
1886
+ client: newClient,
1887
+ });
1888
+ setIsConnecting(false);
1889
+ }
1890
+ })
1891
+ .catch((err) => {
1892
+ if (active) {
1893
+ setError(err instanceof Error ? err : new Error(String(err)));
1894
+ setIsConnecting(false);
1895
+ }
1896
+ });
1897
+ return () => {
1898
+ active = false;
1899
+ };
1900
+ }, [resolvedStack, getOrCreateClient, getClient, lookupKey, lookupOptions]);
1901
+ React.useEffect(() => subscribeToClientChanges((change) => {
1902
+ if (change && change.cacheKey !== lookupKey)
1903
+ return;
1904
+ const sharedClient = getClient(resolvedStack, lookupOptions);
1905
+ const current = clientStateRef.current;
1906
+ if (change?.status === 'connecting') {
1907
+ const next = { lookupKey, client: null };
1908
+ clientStateRef.current = next;
1909
+ setClientState(next);
1910
+ setIsConnecting(true);
1911
+ setError(null);
1912
+ setSocketIssue(null);
1913
+ return;
1914
+ }
1915
+ if (change?.status === 'error') {
1916
+ const next = { lookupKey, client: null };
1917
+ clientStateRef.current = next;
1918
+ setClientState(next);
1919
+ setIsConnecting(false);
1920
+ setError(change.error ?? new Error('Arete connection attempt failed'));
1921
+ setSocketIssue(null);
1922
+ return;
1923
+ }
1924
+ if (current.lookupKey === lookupKey && current.client === sharedClient)
1925
+ return;
1926
+ const next = { lookupKey, client: sharedClient };
1927
+ clientStateRef.current = next;
1928
+ setClientState(next);
1929
+ setIsConnecting(sharedClient === null);
1930
+ setError(null);
1931
+ setSocketIssue(null);
1932
+ }), [getClient, lookupKey, lookupOptions, resolvedStack, subscribeToClientChanges]);
1933
+ React.useEffect(() => {
1934
+ if (!client) {
1935
+ setConnectionState('disconnected');
1936
+ return;
1937
+ }
1938
+ setConnectionState(client.connectionState);
1939
+ const unsubscribeState = client.onConnectionStateChange((state, stateError) => {
1940
+ setConnectionState(state);
1941
+ if (state === 'error') {
1942
+ setError(normalizeConnectionError(stateError, 'Arete connection entered an error state'));
1943
+ }
1944
+ else if (state === 'connected') {
1945
+ setError(null);
1946
+ setSocketIssue(null);
1947
+ }
1948
+ });
1949
+ const unsubscribeIssue = client.onSocketIssue((issue) => {
1950
+ setSocketIssue(issue);
1951
+ });
1952
+ return () => {
1953
+ unsubscribeIssue();
1954
+ unsubscribeState();
1955
+ };
1956
+ }, [client]);
1957
+ const retry = React.useCallback(() => {
1958
+ if (retryPromiseRef.current?.lookupKey === lookupKey) {
1959
+ return retryPromiseRef.current.promise;
1960
+ }
1961
+ const retryLookupKey = lookupKey;
1962
+ setError(null);
1963
+ setSocketIssue(null);
1964
+ setIsConnecting(true);
1965
+ setClientState({ lookupKey: retryLookupKey, client: null });
1966
+ const attempt = retryClient(resolvedStack, lookupOptions).then((newClient) => {
1967
+ if (lookupKeyRef.current === retryLookupKey) {
1968
+ setClientState({
1969
+ lookupKey: retryLookupKey,
1970
+ client: newClient,
1971
+ });
1972
+ setIsConnecting(false);
1973
+ }
1974
+ });
1975
+ const tracked = attempt.catch((value) => {
1976
+ const retryError = normalizeConnectionError(value, 'Arete connection retry failed');
1977
+ if (lookupKeyRef.current === retryLookupKey) {
1978
+ setError(retryError);
1979
+ setIsConnecting(false);
1980
+ }
1981
+ throw retryError;
1982
+ }).finally(() => {
1983
+ if (retryPromiseRef.current?.promise === tracked) {
1984
+ retryPromiseRef.current = null;
1985
+ }
1986
+ });
1987
+ retryPromiseRef.current = { lookupKey, promise: tracked };
1988
+ return tracked;
1989
+ }, [lookupKey, lookupOptions, retryClient, resolvedStack]);
1990
+ const views = React.useMemo(() => {
1991
+ const result = {};
1992
+ for (const [viewName, viewGroup] of Object.entries(resolvedStack.views)) {
1993
+ result[viewName] = {};
1994
+ if (typeof viewGroup === 'object' && viewGroup !== null) {
1995
+ for (const [subViewName, viewDef] of Object.entries(viewGroup)) {
1996
+ if (!viewDef || typeof viewDef !== 'object' || !('mode' in viewDef))
1997
+ continue;
1998
+ if (viewDef.mode === 'state') {
1999
+ result[viewName][subViewName] = createStateViewHook(viewDef, client);
2000
+ }
2001
+ else if (viewDef.mode === 'list') {
2002
+ result[viewName][subViewName] = createListViewHook(viewDef, client);
2003
+ }
2004
+ }
2005
+ }
2006
+ }
2007
+ return result;
2008
+ }, [resolvedStack, client]);
2009
+ const programs = React.useMemo(() => {
2010
+ if (!client) {
2011
+ // Placeholder hooks so components can render (and keep hook order)
2012
+ // before the client connects; submitting throws "not connected".
2013
+ return buildDisconnectedProgramHooks();
2014
+ }
2015
+ return buildProgramHookInterfaces(client.programs, client, useInstructionMutation, { defaultReconciliation: transport !== 'http' });
2016
+ }, [client, transport]);
2017
+ const connectedRead = (client?.read ?? null);
2018
+ const readArgCounts = sdk.getStackRuntimeExtensions(resolvedStack)?.readArgCounts;
2019
+ const read = React.useMemo(() => buildReadInterfaces(connectedRead, readArgCounts), [connectedRead, readArgCounts]);
2020
+ const isClientPending = clientState.lookupKey !== lookupKey || isConnecting;
2021
+ const visibleError = clientState.lookupKey === lookupKey ? error : null;
2022
+ const status = isClientPending
2023
+ ? 'connecting'
2024
+ : visibleError
2025
+ ? 'error'
2026
+ : client
2027
+ ? connectionState
2028
+ : 'disconnected';
2029
+ return {
2030
+ views: views,
2031
+ queries: (client?.queries ?? {}),
2032
+ programs: programs,
2033
+ chain: client?.chain ?? null,
2034
+ zustandStore: client?.store?.store ?? null,
2035
+ client,
2036
+ read,
2037
+ reads: read,
2038
+ addresses: (client?.addresses
2039
+ ?? resolvedStack.addresses),
2040
+ constants: (client?.constants
2041
+ ?? resolvedStack.constants),
2042
+ defaults: (client?.defaults
2043
+ ?? resolvedStack.defaults),
2044
+ math: (client?.math
2045
+ ?? resolvedStack.math),
2046
+ connectionState: client ? connectionState : 'disconnected',
2047
+ status,
2048
+ isConnected: Boolean(client) && connectionState === 'connected',
2049
+ isLoading: isClientPending,
2050
+ canRetry: config.autoConnect !== false
2051
+ && (status === 'error' || status === 'disconnected'),
2052
+ error: visibleError,
2053
+ socketIssue: clientState.lookupKey === lookupKey ? socketIssue : null,
2054
+ retry,
2055
+ };
2056
+ }
2057
+
2058
+ /**
2059
+ * Bind the React SDK to one generated stack without ambient module
2060
+ * augmentation. The returned hook remains explicit through an app-specific
2061
+ * name such as `useOre`, while the generic provider and hook remain available
2062
+ * for multi-stack applications.
2063
+ */
2064
+ function createAreteReact(stack) {
2065
+ function Provider({ children, ...config }) {
2066
+ return (React.createElement(AreteProvider, { ...config, stack: stack }, children));
2067
+ }
2068
+ function useBoundArete(options) {
2069
+ return useArete(stack, options);
2070
+ }
2071
+ return {
2072
+ Provider,
2073
+ useArete: useBoundArete,
2074
+ stack,
2075
+ };
2076
+ }
2077
+
2078
+ /**
2079
+ * Aggregate the status of connection, view, and read hook results into loading,
2080
+ * refreshing, and error lists. Pure function; call it during render.
2081
+ *
2082
+ * ```tsx
2083
+ * const streams = summarizeStatuses({ Board: board, Round: round, Miner: authority && miner });
2084
+ * {streams.loading.length > 0 && <p>Loading: {streams.loading.join(', ')}</p>}
2085
+ * {streams.errors.length > 0 && <p role="alert">{streams.errors.join(' · ')}</p>}
2086
+ * ```
2087
+ */
2088
+ function summarizeStatuses(sources) {
2089
+ const loading = [];
2090
+ const errors = [];
2091
+ const refreshing = [];
2092
+ for (const [name, source] of Object.entries(sources)) {
2093
+ if (!source)
2094
+ continue;
2095
+ if (source.isLoading)
2096
+ loading.push(name);
2097
+ if (source.isRefreshing)
2098
+ refreshing.push(name);
2099
+ if (source.error)
2100
+ errors.push(`${name}: ${source.error.message}`);
2101
+ }
2102
+ return {
2103
+ isLoading: loading.length > 0,
2104
+ hasError: errors.length > 0,
2105
+ isRefreshing: refreshing.length > 0,
2106
+ loading,
2107
+ errors,
2108
+ refreshing,
2109
+ };
2110
+ }
2111
+ /** @deprecated Use summarizeStatuses; it also accepts connection and read results. */
2112
+ const summarizeViews = summarizeStatuses;
2113
+
2114
+ const chainIds = new WeakMap();
2115
+ let nextChainId = 1;
2116
+ function chainKey(chain) {
2117
+ if (!chain) {
2118
+ return null;
2119
+ }
2120
+ const existing = chainIds.get(chain);
2121
+ if (existing !== undefined) {
2122
+ return existing;
2123
+ }
2124
+ const id = nextChainId++;
2125
+ chainIds.set(chain, id);
2126
+ return id;
2127
+ }
2128
+ function abortError() {
2129
+ const error = new Error('Chain read was aborted');
2130
+ error.name = 'AbortError';
2131
+ return error;
2132
+ }
2133
+ function useNativeBalance(chain, address, options = {}) {
2134
+ const enabled = (options.enabled ?? true) && Boolean(chain && address);
2135
+ const minContextSlot = options.minContextSlot;
2136
+ return useAsyncRead(['native-balance', chainKey(chain), address, minContextSlot?.toString()], async ({ signal }) => {
2137
+ if (!chain || !address) {
2138
+ throw new Error('Chain client and address are required for a native balance read');
2139
+ }
2140
+ if (signal.aborted)
2141
+ throw abortError();
2142
+ const result = await chain.nativeBalance(address, { minContextSlot });
2143
+ if (signal.aborted)
2144
+ throw abortError();
2145
+ return result;
2146
+ }, { enabled, initialData: options.initialData });
2147
+ }
2148
+ function useTokenBalance(chain, input, options = {}) {
2149
+ const enabled = (options.enabled ?? true) && Boolean(chain && input);
2150
+ const minContextSlot = options.minContextSlot;
2151
+ return useAsyncRead([
2152
+ 'token-balance',
2153
+ chainKey(chain),
2154
+ input?.owner,
2155
+ input?.mint,
2156
+ input?.tokenProgram,
2157
+ minContextSlot?.toString(),
2158
+ ], async ({ signal }) => {
2159
+ if (!chain || !input) {
2160
+ throw new Error('Chain client and token input are required for a token balance read');
2161
+ }
2162
+ if (signal.aborted)
2163
+ throw abortError();
2164
+ const result = await chain.balance(input, { minContextSlot });
2165
+ if (signal.aborted)
2166
+ throw abortError();
2167
+ return result;
2168
+ }, { enabled, initialData: options.initialData });
2169
+ }
2170
+
2171
+ Object.defineProperty(exports, 'Arete', {
2172
+ enumerable: true,
2173
+ get: function () { return sdk.Arete; }
2174
+ });
2175
+ Object.defineProperty(exports, 'AreteError', {
2176
+ enumerable: true,
2177
+ get: function () { return sdk.AreteError; }
2178
+ });
2179
+ Object.defineProperty(exports, 'ConnectionManager', {
2180
+ enumerable: true,
2181
+ get: function () { return sdk.ConnectionManager; }
2182
+ });
2183
+ Object.defineProperty(exports, 'DEFAULT_CONFIG', {
2184
+ enumerable: true,
2185
+ get: function () { return sdk.DEFAULT_CONFIG; }
2186
+ });
2187
+ Object.defineProperty(exports, 'DEFAULT_MAX_ENTRIES_PER_VIEW', {
2188
+ enumerable: true,
2189
+ get: function () { return sdk.DEFAULT_MAX_ENTRIES_PER_VIEW; }
2190
+ });
2191
+ Object.defineProperty(exports, 'FrameProcessor', {
2192
+ enumerable: true,
2193
+ get: function () { return sdk.FrameProcessor; }
2194
+ });
2195
+ Object.defineProperty(exports, 'InstructionError', {
2196
+ enumerable: true,
2197
+ get: function () { return sdk.InstructionError; }
2198
+ });
2199
+ Object.defineProperty(exports, 'MemoryAdapter', {
2200
+ enumerable: true,
2201
+ get: function () { return sdk.MemoryAdapter; }
2202
+ });
2203
+ Object.defineProperty(exports, 'OperationCallbackError', {
2204
+ enumerable: true,
2205
+ get: function () { return sdk.OperationCallbackError; }
2206
+ });
2207
+ Object.defineProperty(exports, 'OperationExecutionError', {
2208
+ enumerable: true,
2209
+ get: function () { return sdk.OperationExecutionError; }
2210
+ });
2211
+ Object.defineProperty(exports, 'ProcessedSlotTimeoutError', {
2212
+ enumerable: true,
2213
+ get: function () { return sdk.ProcessedSlotTimeoutError; }
2214
+ });
2215
+ Object.defineProperty(exports, 'QueryStore', {
2216
+ enumerable: true,
2217
+ get: function () { return sdk.QueryStore; }
2218
+ });
2219
+ Object.defineProperty(exports, 'ReadRequestError', {
2220
+ enumerable: true,
2221
+ get: function () { return sdk.ReadRequestError; }
2222
+ });
2223
+ Object.defineProperty(exports, 'SubscriptionRegistry', {
2224
+ enumerable: true,
2225
+ get: function () { return sdk.SubscriptionRegistry; }
2226
+ });
2227
+ Object.defineProperty(exports, 'TransactionExecutionError', {
2228
+ enumerable: true,
2229
+ get: function () { return sdk.TransactionExecutionError; }
2230
+ });
2231
+ Object.defineProperty(exports, 'buildInstruction', {
2232
+ enumerable: true,
2233
+ get: function () { return sdk.buildInstruction; }
2234
+ });
2235
+ Object.defineProperty(exports, 'canonicalQueryKey', {
2236
+ enumerable: true,
2237
+ get: function () { return sdk.canonicalQueryKey; }
2238
+ });
2239
+ Object.defineProperty(exports, 'createInstructionExecutor', {
2240
+ enumerable: true,
2241
+ get: function () { return sdk.createInstructionExecutor; }
2242
+ });
2243
+ Object.defineProperty(exports, 'createInstructionHandler', {
2244
+ enumerable: true,
2245
+ get: function () { return sdk.createInstructionHandler; }
2246
+ });
2247
+ Object.defineProperty(exports, 'createPublicKeySeed', {
2248
+ enumerable: true,
2249
+ get: function () { return sdk.createPublicKeySeed; }
2250
+ });
2251
+ Object.defineProperty(exports, 'createSeed', {
2252
+ enumerable: true,
2253
+ get: function () { return sdk.createSeed; }
2254
+ });
2255
+ Object.defineProperty(exports, 'derivePda', {
2256
+ enumerable: true,
2257
+ get: function () { return sdk.derivePda; }
2258
+ });
2259
+ Object.defineProperty(exports, 'executeInstruction', {
2260
+ enumerable: true,
2261
+ get: function () { return sdk.executeInstruction; }
2262
+ });
2263
+ Object.defineProperty(exports, 'formatProgramError', {
2264
+ enumerable: true,
2265
+ get: function () { return sdk.formatProgramError; }
2266
+ });
2267
+ Object.defineProperty(exports, 'formatRawToUi', {
2268
+ enumerable: true,
2269
+ get: function () { return sdk.formatRawToUi; }
2270
+ });
2271
+ Object.defineProperty(exports, 'getTransactionFailureOutcome', {
2272
+ enumerable: true,
2273
+ get: function () { return sdk.getTransactionFailureOutcome; }
2274
+ });
2275
+ Object.defineProperty(exports, 'isErrorFrame', {
2276
+ enumerable: true,
2277
+ get: function () { return sdk.isErrorFrame; }
2278
+ });
2279
+ Object.defineProperty(exports, 'isHostedAreteEndpoint', {
2280
+ enumerable: true,
2281
+ get: function () { return sdk.isHostedAreteEndpoint; }
2282
+ });
2283
+ Object.defineProperty(exports, 'isSnapshotFrame', {
2284
+ enumerable: true,
2285
+ get: function () { return sdk.isSnapshotFrame; }
2286
+ });
2287
+ Object.defineProperty(exports, 'isSubscribedFrame', {
2288
+ enumerable: true,
2289
+ get: function () { return sdk.isSubscribedFrame; }
2290
+ });
2291
+ Object.defineProperty(exports, 'isUnsubscribedFrame', {
2292
+ enumerable: true,
2293
+ get: function () { return sdk.isUnsubscribedFrame; }
2294
+ });
2295
+ Object.defineProperty(exports, 'isValidFrame', {
2296
+ enumerable: true,
2297
+ get: function () { return sdk.isValidFrame; }
2298
+ });
2299
+ Object.defineProperty(exports, 'normalizeSubscriptionQuery', {
2300
+ enumerable: true,
2301
+ get: function () { return sdk.normalizeSubscriptionQuery; }
2302
+ });
2303
+ Object.defineProperty(exports, 'parseFrame', {
2304
+ enumerable: true,
2305
+ get: function () { return sdk.parseFrame; }
2306
+ });
2307
+ Object.defineProperty(exports, 'parseFrameFromBlob', {
2308
+ enumerable: true,
2309
+ get: function () { return sdk.parseFrameFromBlob; }
2310
+ });
2311
+ Object.defineProperty(exports, 'parseInstructionError', {
2312
+ enumerable: true,
2313
+ get: function () { return sdk.parseInstructionError; }
2314
+ });
2315
+ Object.defineProperty(exports, 'resolveAccounts', {
2316
+ enumerable: true,
2317
+ get: function () { return sdk.resolveAccounts; }
2318
+ });
2319
+ Object.defineProperty(exports, 'safeToRawAmount', {
2320
+ enumerable: true,
2321
+ get: function () { return sdk.safeToRawAmount; }
2322
+ });
2323
+ Object.defineProperty(exports, 'serializeInstructionData', {
2324
+ enumerable: true,
2325
+ get: function () { return sdk.serializeInstructionData; }
2326
+ });
2327
+ Object.defineProperty(exports, 'serializeViewKey', {
2328
+ enumerable: true,
2329
+ get: function () { return sdk.serializeViewKey; }
2330
+ });
2331
+ Object.defineProperty(exports, 'stringifyBigints', {
2332
+ enumerable: true,
2333
+ get: function () { return sdk.stringifyBigints; }
2334
+ });
2335
+ Object.defineProperty(exports, 'toRawAmount', {
2336
+ enumerable: true,
2337
+ get: function () { return sdk.toRawAmount; }
2338
+ });
2339
+ Object.defineProperty(exports, 'unwrapOperationExecutionError', {
2340
+ enumerable: true,
2341
+ get: function () { return sdk.unwrapOperationExecutionError; }
2342
+ });
2343
+ Object.defineProperty(exports, 'validateAccountResolution', {
2344
+ enumerable: true,
2345
+ get: function () { return sdk.validateAccountResolution; }
2346
+ });
2347
+ Object.defineProperty(exports, 'withPrograms', {
2348
+ enumerable: true,
2349
+ get: function () { return sdk.withPrograms; }
2350
+ });
2351
+ exports.AreteProvider = AreteProvider;
2352
+ exports.DEFAULT_RECONCILIATION_TIMEOUT_MS = DEFAULT_RECONCILIATION_TIMEOUT_MS;
2353
+ exports.ZustandAdapter = ZustandAdapter;
2354
+ exports.createAreteReact = createAreteReact;
2355
+ exports.createProcessedSlotReconciliation = createProcessedSlotReconciliation;
2356
+ exports.reconcileProcessedSlot = reconcileProcessedSlot;
2357
+ exports.summarizeStatuses = summarizeStatuses;
2358
+ exports.summarizeViews = summarizeViews;
2359
+ exports.useArete = useArete;
2360
+ exports.useAreteContext = useAreteContext;
2361
+ exports.useAsyncRead = useAsyncRead;
2362
+ exports.useConnectionState = useConnectionState;
2363
+ exports.useInstructionMutation = useInstructionMutation;
2364
+ exports.useNativeBalance = useNativeBalance;
2365
+ exports.useReconcileProcessedSlot = useReconcileProcessedSlot;
2366
+ exports.useTokenBalance = useTokenBalance;
2367
+ //# sourceMappingURL=index.cjs.map