@0xobelisk/react 1.2.0-pre.64
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/LICENSE +92 -0
- package/README.md +564 -0
- package/dist/chunk-2WEYKH27.mjs +344 -0
- package/dist/chunk-2WEYKH27.mjs.map +1 -0
- package/dist/chunk-LQ3XXOG4.mjs +342 -0
- package/dist/chunk-LQ3XXOG4.mjs.map +1 -0
- package/dist/chunk-TTYSS65P.mjs +345 -0
- package/dist/chunk-TTYSS65P.mjs.map +1 -0
- package/dist/chunk-XI35QBSQ.mjs +338 -0
- package/dist/chunk-XI35QBSQ.mjs.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +27 -0
- package/dist/index.mjs.map +1 -0
- package/dist/sui/config.d.ts +52 -0
- package/dist/sui/hooks.d.ts +123 -0
- package/dist/sui/index.d.mts +353 -0
- package/dist/sui/index.d.ts +353 -0
- package/dist/sui/index.js +374 -0
- package/dist/sui/index.js.map +1 -0
- package/dist/sui/index.mjs +27 -0
- package/dist/sui/index.mjs.map +1 -0
- package/dist/sui/provider.d.ts +137 -0
- package/dist/sui/types.d.ts +87 -0
- package/dist/sui/utils.d.ts +35 -0
- package/package.json +103 -0
- package/src/index.ts +16 -0
- package/src/sui/config.ts +90 -0
- package/src/sui/hooks.ts +144 -0
- package/src/sui/index.ts +37 -0
- package/src/sui/provider.tsx +341 -0
- package/src/sui/types.ts +99 -0
- package/src/sui/utils.ts +192 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dubhe Provider - useRef Pattern for Client Management
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - 🎯 Single client instances across application lifecycle
|
|
6
|
+
* - ⚡ useRef-based storage (no re-initialization on re-renders)
|
|
7
|
+
* - 🔧 Provider pattern for dependency injection
|
|
8
|
+
* - 🛡️ Complete type safety with strict TypeScript
|
|
9
|
+
* - 📦 Context-based client sharing
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { createContext, useContext, useRef, ReactNode } from 'react';
|
|
13
|
+
import { Dubhe } from '@0xobelisk/sui-client';
|
|
14
|
+
import { createDubheGraphqlClient } from '@0xobelisk/graphql-client';
|
|
15
|
+
import { createECSWorld } from '@0xobelisk/ecs';
|
|
16
|
+
import { useDubheConfig } from './config';
|
|
17
|
+
import type { DubheConfig, DubheReturn } from './types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Context interface for Dubhe client instances
|
|
21
|
+
* All clients are stored using useRef to ensure single initialization
|
|
22
|
+
*/
|
|
23
|
+
interface DubheContextValue {
|
|
24
|
+
getContract: () => Dubhe;
|
|
25
|
+
getGraphqlClient: () => any | null;
|
|
26
|
+
getEcsWorld: () => any | null;
|
|
27
|
+
getAddress: () => string;
|
|
28
|
+
getMetrics: () => {
|
|
29
|
+
initTime: number;
|
|
30
|
+
requestCount: number;
|
|
31
|
+
lastActivity: number;
|
|
32
|
+
};
|
|
33
|
+
config: DubheConfig;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Context for sharing Dubhe clients across the application
|
|
38
|
+
* Uses useRef pattern to ensure clients are created only once
|
|
39
|
+
*/
|
|
40
|
+
const DubheContext = createContext<DubheContextValue | null>(null);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Props interface for DubheProvider component
|
|
44
|
+
*/
|
|
45
|
+
interface DubheProviderProps {
|
|
46
|
+
/** Configuration for Dubhe initialization */
|
|
47
|
+
config: Partial<DubheConfig>;
|
|
48
|
+
/** Child components that will have access to Dubhe clients */
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* DubheProvider Component - useRef Pattern Implementation
|
|
54
|
+
*
|
|
55
|
+
* This Provider uses useRef to store client instances, ensuring they are:
|
|
56
|
+
* 1. Created only once during component lifecycle
|
|
57
|
+
* 2. Persisted across re-renders without re-initialization
|
|
58
|
+
* 3. Shared efficiently via React Context
|
|
59
|
+
*
|
|
60
|
+
* Key advantages over useMemo:
|
|
61
|
+
* - useRef guarantees single initialization (useMemo can re-run on dependency changes)
|
|
62
|
+
* - No dependency array needed (eliminates potential re-initialization bugs)
|
|
63
|
+
* - Better performance for heavy client objects
|
|
64
|
+
* - Clearer separation of concerns via Provider pattern
|
|
65
|
+
*
|
|
66
|
+
* @param props - Provider props containing config and children
|
|
67
|
+
* @returns Provider component wrapping children with Dubhe context
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // App root setup
|
|
72
|
+
* function App() {
|
|
73
|
+
* const dubheConfig = {
|
|
74
|
+
* network: 'devnet',
|
|
75
|
+
* packageId: '0x123...',
|
|
76
|
+
* metadata: contractMetadata,
|
|
77
|
+
* credentials: {
|
|
78
|
+
* secretKey: process.env.NEXT_PUBLIC_PRIVATE_KEY
|
|
79
|
+
* }
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* return (
|
|
83
|
+
* <DubheProvider config={dubheConfig}>
|
|
84
|
+
* <MyApplication />
|
|
85
|
+
* </DubheProvider>
|
|
86
|
+
* );
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function DubheProvider({ config, children }: DubheProviderProps) {
|
|
91
|
+
// Merge configuration with defaults (only runs once)
|
|
92
|
+
const finalConfig = useDubheConfig(config);
|
|
93
|
+
|
|
94
|
+
// Track initialization start time (useRef ensures single timestamp)
|
|
95
|
+
const startTimeRef = useRef<number>(performance.now());
|
|
96
|
+
|
|
97
|
+
// useRef for contract instance - guarantees single initialization
|
|
98
|
+
// Unlike useMemo, useRef.current is never re-calculated
|
|
99
|
+
const contractRef = useRef<Dubhe | undefined>(undefined);
|
|
100
|
+
const getContract = (): Dubhe => {
|
|
101
|
+
if (!contractRef.current) {
|
|
102
|
+
try {
|
|
103
|
+
console.log('Initializing Dubhe contract instance (one-time)');
|
|
104
|
+
contractRef.current = new Dubhe({
|
|
105
|
+
networkType: finalConfig.network,
|
|
106
|
+
packageId: finalConfig.packageId,
|
|
107
|
+
metadata: finalConfig.metadata,
|
|
108
|
+
secretKey: finalConfig.credentials?.secretKey
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Contract initialization failed:', error);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return contractRef.current;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// useRef for GraphQL client instance - single initialization guaranteed
|
|
119
|
+
const graphqlClientRef = useRef<any | null>(null);
|
|
120
|
+
const hasInitializedGraphql = useRef(false);
|
|
121
|
+
const getGraphqlClient = (): any | null => {
|
|
122
|
+
if (!hasInitializedGraphql.current && finalConfig.dubheMetadata) {
|
|
123
|
+
try {
|
|
124
|
+
console.log('Initializing GraphQL client instance (one-time)');
|
|
125
|
+
graphqlClientRef.current = createDubheGraphqlClient({
|
|
126
|
+
endpoint: finalConfig.endpoints?.graphql || 'http://localhost:4000/graphql',
|
|
127
|
+
subscriptionEndpoint: finalConfig.endpoints?.websocket || 'ws://localhost:4000/graphql',
|
|
128
|
+
dubheMetadata: finalConfig.dubheMetadata
|
|
129
|
+
});
|
|
130
|
+
hasInitializedGraphql.current = true;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('GraphQL client initialization failed:', error);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return graphqlClientRef.current;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// useRef for ECS World instance - depends on GraphQL client
|
|
140
|
+
const ecsWorldRef = useRef<any | null>(null);
|
|
141
|
+
const hasInitializedEcs = useRef(false);
|
|
142
|
+
const getEcsWorld = (): any | null => {
|
|
143
|
+
const graphqlClient = getGraphqlClient();
|
|
144
|
+
if (!hasInitializedEcs.current && graphqlClient) {
|
|
145
|
+
try {
|
|
146
|
+
console.log('Initializing ECS World instance (one-time)');
|
|
147
|
+
ecsWorldRef.current = createECSWorld(graphqlClient, {
|
|
148
|
+
queryConfig: {
|
|
149
|
+
enableBatchOptimization: finalConfig.options?.enableBatchOptimization ?? true,
|
|
150
|
+
defaultCacheTimeout: finalConfig.options?.cacheTimeout ?? 5000
|
|
151
|
+
},
|
|
152
|
+
subscriptionConfig: {
|
|
153
|
+
defaultDebounceMs: finalConfig.options?.debounceMs ?? 100,
|
|
154
|
+
reconnectOnError: finalConfig.options?.reconnectOnError ?? true
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
hasInitializedEcs.current = true;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('ECS World initialization failed:', error);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return ecsWorldRef.current;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Address getter - calculated from contract
|
|
167
|
+
const getAddress = (): string => {
|
|
168
|
+
return getContract().getAddress();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Metrics getter - performance tracking
|
|
172
|
+
const getMetrics = () => ({
|
|
173
|
+
initTime: performance.now() - (startTimeRef.current || 0),
|
|
174
|
+
requestCount: 0, // Can be enhanced with actual tracking
|
|
175
|
+
lastActivity: Date.now()
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Context value - stable reference (no re-renders for consumers)
|
|
179
|
+
const contextValue: DubheContextValue = {
|
|
180
|
+
getContract,
|
|
181
|
+
getGraphqlClient,
|
|
182
|
+
getEcsWorld,
|
|
183
|
+
getAddress,
|
|
184
|
+
getMetrics,
|
|
185
|
+
config: finalConfig
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return <DubheContext.Provider value={contextValue}>{children}</DubheContext.Provider>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Custom hook to access Dubhe context
|
|
193
|
+
* Provides type-safe access to all Dubhe client instances
|
|
194
|
+
*
|
|
195
|
+
* @returns DubheContextValue with all client getters and config
|
|
196
|
+
* @throws Error if used outside of DubheProvider
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* function MyComponent() {
|
|
201
|
+
* const dubheContext = useDubheContext();
|
|
202
|
+
*
|
|
203
|
+
* const contract = dubheContext.getContract();
|
|
204
|
+
* const graphqlClient = dubheContext.getGraphqlClient();
|
|
205
|
+
* const ecsWorld = dubheContext.getEcsWorld();
|
|
206
|
+
* const address = dubheContext.getAddress();
|
|
207
|
+
*
|
|
208
|
+
* return <div>Connected as {address}</div>;
|
|
209
|
+
* }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export function useDubheContext(): DubheContextValue {
|
|
213
|
+
const context = useContext(DubheContext);
|
|
214
|
+
|
|
215
|
+
if (!context) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
'useDubheContext must be used within a DubheProvider. ' +
|
|
218
|
+
'Make sure to wrap your app with <DubheProvider config={...}>'
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return context;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Enhanced hook that mimics the original useDubhe API
|
|
227
|
+
* Uses the Provider pattern internally but maintains backward compatibility
|
|
228
|
+
*
|
|
229
|
+
* @returns DubheReturn object with all instances and metadata
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* function MyComponent() {
|
|
234
|
+
* const { contract, graphqlClient, ecsWorld, address } = useDubheFromProvider();
|
|
235
|
+
*
|
|
236
|
+
* const handleTransaction = async () => {
|
|
237
|
+
* const tx = new Transaction();
|
|
238
|
+
* await contract.tx.my_system.my_method({ tx });
|
|
239
|
+
* };
|
|
240
|
+
*
|
|
241
|
+
* return <button onClick={handleTransaction}>Execute</button>;
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function useDubheFromProvider(): DubheReturn {
|
|
246
|
+
const context = useDubheContext();
|
|
247
|
+
|
|
248
|
+
// Get instances (lazy initialization via getters)
|
|
249
|
+
const contract = context.getContract();
|
|
250
|
+
const graphqlClient = context.getGraphqlClient();
|
|
251
|
+
const ecsWorld = context.getEcsWorld();
|
|
252
|
+
const address = context.getAddress();
|
|
253
|
+
const metrics = context.getMetrics();
|
|
254
|
+
|
|
255
|
+
// Enhanced contract with additional methods (similar to original implementation)
|
|
256
|
+
const enhancedContract = contract as any;
|
|
257
|
+
|
|
258
|
+
// Add transaction methods with error handling (if not already added)
|
|
259
|
+
if (!enhancedContract.txWithOptions) {
|
|
260
|
+
enhancedContract.txWithOptions = (system: string, method: string, options: any = {}) => {
|
|
261
|
+
return async (params: any) => {
|
|
262
|
+
try {
|
|
263
|
+
const startTime = performance.now();
|
|
264
|
+
const result = await contract.tx[system][method](params);
|
|
265
|
+
const executionTime = performance.now() - startTime;
|
|
266
|
+
|
|
267
|
+
if (process.env.NODE_ENV === 'development') {
|
|
268
|
+
console.log(
|
|
269
|
+
`Transaction ${system}.${method} completed in ${executionTime.toFixed(2)}ms`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
options.onSuccess?.(result);
|
|
274
|
+
return result;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
options.onError?.(error);
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Add query methods with performance tracking (if not already added)
|
|
284
|
+
if (!enhancedContract.queryWithOptions) {
|
|
285
|
+
enhancedContract.queryWithOptions = (system: string, method: string, options: any = {}) => {
|
|
286
|
+
return async (params: any) => {
|
|
287
|
+
const startTime = performance.now();
|
|
288
|
+
const result = await contract.query[system][method](params);
|
|
289
|
+
const executionTime = performance.now() - startTime;
|
|
290
|
+
|
|
291
|
+
if (process.env.NODE_ENV === 'development') {
|
|
292
|
+
console.log(`Query ${system}.${method} completed in ${executionTime.toFixed(2)}ms`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return result;
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
contract: enhancedContract,
|
|
302
|
+
graphqlClient,
|
|
303
|
+
ecsWorld,
|
|
304
|
+
metadata: context.config.metadata,
|
|
305
|
+
network: context.config.network,
|
|
306
|
+
packageId: context.config.packageId,
|
|
307
|
+
dubheSchemaId: context.config.dubheSchemaId,
|
|
308
|
+
address,
|
|
309
|
+
options: context.config.options,
|
|
310
|
+
metrics
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Individual client hooks for components that only need specific instances
|
|
316
|
+
* These are more efficient than useDubheFromProvider for single-client usage
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Hook for accessing only the Dubhe contract instance
|
|
321
|
+
*/
|
|
322
|
+
export function useDubheContractFromProvider(): Dubhe {
|
|
323
|
+
const { contract } = useDubheFromProvider();
|
|
324
|
+
return contract;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Hook for accessing only the GraphQL client instance
|
|
329
|
+
*/
|
|
330
|
+
export function useDubheGraphQLFromProvider(): any | null {
|
|
331
|
+
const { getGraphqlClient } = useDubheContext();
|
|
332
|
+
return getGraphqlClient();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Hook for accessing only the ECS World instance
|
|
337
|
+
*/
|
|
338
|
+
export function useDubheECSFromProvider(): any | null {
|
|
339
|
+
const { getEcsWorld } = useDubheContext();
|
|
340
|
+
return getEcsWorld();
|
|
341
|
+
}
|
package/src/sui/types.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { SuiMoveNormalizedModules, Dubhe } from '@0xobelisk/sui-client';
|
|
2
|
+
import type { DubheGraphqlClient } from '@0xobelisk/graphql-client';
|
|
3
|
+
import type { DubheECSWorld } from '@0xobelisk/ecs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Network type
|
|
7
|
+
*/
|
|
8
|
+
export type NetworkType = 'mainnet' | 'testnet' | 'devnet' | 'localnet';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Modern Dubhe client configuration for auto-initialization
|
|
12
|
+
*/
|
|
13
|
+
export interface DubheConfig {
|
|
14
|
+
/** Network type */
|
|
15
|
+
network: NetworkType;
|
|
16
|
+
/** Contract package ID */
|
|
17
|
+
packageId: string;
|
|
18
|
+
/** Dubhe Schema ID (optional, for enhanced features) */
|
|
19
|
+
dubheSchemaId: string;
|
|
20
|
+
/** Contract metadata (required for contract instantiation) */
|
|
21
|
+
metadata: SuiMoveNormalizedModules;
|
|
22
|
+
/** Dubhe metadata (enables GraphQL/ECS features) */
|
|
23
|
+
dubheMetadata?: any;
|
|
24
|
+
/** Authentication credentials */
|
|
25
|
+
credentials?: {
|
|
26
|
+
secretKey?: string;
|
|
27
|
+
mnemonics?: string;
|
|
28
|
+
};
|
|
29
|
+
/** Service endpoints configuration */
|
|
30
|
+
endpoints?: {
|
|
31
|
+
graphql?: string;
|
|
32
|
+
websocket?: string;
|
|
33
|
+
};
|
|
34
|
+
/** Performance and behavior options */
|
|
35
|
+
options?: {
|
|
36
|
+
/** Enable batch query optimization */
|
|
37
|
+
enableBatchOptimization?: boolean;
|
|
38
|
+
/** Default cache timeout (milliseconds) */
|
|
39
|
+
cacheTimeout?: number;
|
|
40
|
+
/** Request debounce delay (milliseconds) */
|
|
41
|
+
debounceMs?: number;
|
|
42
|
+
/** Auto-reconnect on WebSocket errors */
|
|
43
|
+
reconnectOnError?: boolean;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Return type for the main useDubhe hook
|
|
49
|
+
*/
|
|
50
|
+
export interface DubheReturn {
|
|
51
|
+
/** Dubhe contract instance with enhanced methods */
|
|
52
|
+
contract: Dubhe & {
|
|
53
|
+
/** Enhanced transaction methods with options */
|
|
54
|
+
txWithOptions?: (
|
|
55
|
+
system: string,
|
|
56
|
+
method: string,
|
|
57
|
+
options?: any
|
|
58
|
+
) => (params: any) => Promise<any>;
|
|
59
|
+
/** Enhanced query methods with options */
|
|
60
|
+
queryWithOptions?: (
|
|
61
|
+
system: string,
|
|
62
|
+
method: string,
|
|
63
|
+
options?: any
|
|
64
|
+
) => (params: any) => Promise<any>;
|
|
65
|
+
};
|
|
66
|
+
/** GraphQL client (null if dubheMetadata not provided) */
|
|
67
|
+
graphqlClient: DubheGraphqlClient | null;
|
|
68
|
+
/** ECS World instance (null if GraphQL not available) */
|
|
69
|
+
ecsWorld: DubheECSWorld | null;
|
|
70
|
+
/** Contract metadata */
|
|
71
|
+
metadata: SuiMoveNormalizedModules;
|
|
72
|
+
/** Network type */
|
|
73
|
+
network: NetworkType;
|
|
74
|
+
/** Package ID */
|
|
75
|
+
packageId: string;
|
|
76
|
+
/** Dubhe Schema ID (if provided) */
|
|
77
|
+
dubheSchemaId?: string;
|
|
78
|
+
/** User address */
|
|
79
|
+
address: string;
|
|
80
|
+
/** Configuration options used */
|
|
81
|
+
options?: {
|
|
82
|
+
enableBatchOptimization?: boolean;
|
|
83
|
+
cacheTimeout?: number;
|
|
84
|
+
debounceMs?: number;
|
|
85
|
+
reconnectOnError?: boolean;
|
|
86
|
+
};
|
|
87
|
+
/** Performance metrics */
|
|
88
|
+
metrics?: {
|
|
89
|
+
initTime?: number;
|
|
90
|
+
requestCount?: number;
|
|
91
|
+
lastActivity?: number;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compatibility alias for DubheReturn
|
|
97
|
+
* @deprecated Use DubheReturn instead for better consistency
|
|
98
|
+
*/
|
|
99
|
+
export type ContractReturn = DubheReturn;
|
package/src/sui/utils.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility Functions for Dubhe Configuration Management
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Configuration validation and error handling
|
|
6
|
+
* - Smart configuration merging with proper type safety
|
|
7
|
+
* - Type-safe configuration validation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DubheConfig, NetworkType } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Merge multiple configuration objects with proper deep merging
|
|
14
|
+
* Later configurations override earlier ones
|
|
15
|
+
*
|
|
16
|
+
* @param baseConfig - Base configuration (usually defaults)
|
|
17
|
+
* @param overrideConfig - Override configuration (user provided)
|
|
18
|
+
* @returns Merged configuration
|
|
19
|
+
*/
|
|
20
|
+
export function mergeConfigurations(
|
|
21
|
+
baseConfig: Partial<DubheConfig>,
|
|
22
|
+
overrideConfig?: Partial<DubheConfig>
|
|
23
|
+
): Partial<DubheConfig> {
|
|
24
|
+
if (!overrideConfig) {
|
|
25
|
+
return { ...baseConfig };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result: Partial<DubheConfig> = { ...baseConfig };
|
|
29
|
+
|
|
30
|
+
// Merge top-level properties
|
|
31
|
+
Object.assign(result, overrideConfig);
|
|
32
|
+
|
|
33
|
+
// Deep merge nested objects
|
|
34
|
+
if (overrideConfig.credentials || baseConfig.credentials) {
|
|
35
|
+
result.credentials = {
|
|
36
|
+
...baseConfig.credentials,
|
|
37
|
+
...overrideConfig.credentials
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (overrideConfig.endpoints || baseConfig.endpoints) {
|
|
42
|
+
result.endpoints = {
|
|
43
|
+
...baseConfig.endpoints,
|
|
44
|
+
...overrideConfig.endpoints
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (overrideConfig.options || baseConfig.options) {
|
|
49
|
+
result.options = {
|
|
50
|
+
...baseConfig.options,
|
|
51
|
+
...overrideConfig.options
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate configuration and ensure required fields are present
|
|
60
|
+
* Throws descriptive errors for missing required fields
|
|
61
|
+
*
|
|
62
|
+
* @param config - Configuration to validate
|
|
63
|
+
* @returns Validated and typed configuration
|
|
64
|
+
* @throws Error if required fields are missing or invalid
|
|
65
|
+
*/
|
|
66
|
+
export function validateConfig(config: Partial<DubheConfig>): DubheConfig {
|
|
67
|
+
const errors: string[] = [];
|
|
68
|
+
|
|
69
|
+
// Check required fields
|
|
70
|
+
if (!config.network) {
|
|
71
|
+
errors.push('network is required');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!config.packageId) {
|
|
75
|
+
errors.push('packageId is required');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!config.metadata) {
|
|
79
|
+
errors.push('metadata is required');
|
|
80
|
+
} else {
|
|
81
|
+
// Basic metadata validation
|
|
82
|
+
if (typeof config.metadata !== 'object') {
|
|
83
|
+
errors.push('metadata must be an object');
|
|
84
|
+
} else if (Object.keys(config.metadata).length === 0) {
|
|
85
|
+
errors.push('metadata cannot be empty');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate network type
|
|
90
|
+
if (config.network && !['mainnet', 'testnet', 'devnet', 'localnet'].includes(config.network)) {
|
|
91
|
+
errors.push(
|
|
92
|
+
`invalid network: ${config.network}. Must be one of: mainnet, testnet, devnet, localnet`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate package ID format (enhanced check)
|
|
97
|
+
if (config.packageId) {
|
|
98
|
+
if (!config.packageId.startsWith('0x')) {
|
|
99
|
+
errors.push('packageId must start with 0x');
|
|
100
|
+
} else if (config.packageId.length < 3) {
|
|
101
|
+
errors.push('packageId must be longer than 0x');
|
|
102
|
+
} else if (!/^0x[a-fA-F0-9]+$/.test(config.packageId)) {
|
|
103
|
+
errors.push('packageId must contain only hexadecimal characters after 0x');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate dubheMetadata if provided
|
|
108
|
+
if (config.dubheMetadata !== undefined) {
|
|
109
|
+
if (typeof config.dubheMetadata !== 'object' || config.dubheMetadata === null) {
|
|
110
|
+
errors.push('dubheMetadata must be an object');
|
|
111
|
+
} else if (!config.dubheMetadata.components && !config.dubheMetadata.resources) {
|
|
112
|
+
errors.push('dubheMetadata must contain components or resources');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Validate credentials if provided
|
|
117
|
+
if (config.credentials) {
|
|
118
|
+
if (config.credentials.secretKey && typeof config.credentials.secretKey !== 'string') {
|
|
119
|
+
errors.push('credentials.secretKey must be a string');
|
|
120
|
+
}
|
|
121
|
+
if (config.credentials.mnemonics && typeof config.credentials.mnemonics !== 'string') {
|
|
122
|
+
errors.push('credentials.mnemonics must be a string');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate URLs if provided
|
|
127
|
+
if (config.endpoints?.graphql && !isValidUrl(config.endpoints.graphql)) {
|
|
128
|
+
errors.push('endpoints.graphql must be a valid URL');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (config.endpoints?.websocket && !isValidUrl(config.endpoints.websocket)) {
|
|
132
|
+
errors.push('endpoints.websocket must be a valid URL');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate numeric options
|
|
136
|
+
if (
|
|
137
|
+
config.options?.cacheTimeout !== undefined &&
|
|
138
|
+
(typeof config.options.cacheTimeout !== 'number' || config.options.cacheTimeout < 0)
|
|
139
|
+
) {
|
|
140
|
+
errors.push('options.cacheTimeout must be a non-negative number');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
config.options?.debounceMs !== undefined &&
|
|
145
|
+
(typeof config.options.debounceMs !== 'number' || config.options.debounceMs < 0)
|
|
146
|
+
) {
|
|
147
|
+
errors.push('options.debounceMs must be a non-negative number');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (errors.length > 0) {
|
|
151
|
+
const errorMessage = `Invalid Dubhe configuration (${errors.length} error${errors.length > 1 ? 's' : ''}):\n${errors.map((e) => `- ${e}`).join('\n')}`;
|
|
152
|
+
console.error('Configuration validation failed:', { errors, config });
|
|
153
|
+
throw new Error(errorMessage);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return config as DubheConfig;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Simple URL validation helper
|
|
161
|
+
*
|
|
162
|
+
* @param url - URL string to validate
|
|
163
|
+
* @returns true if URL is valid, false otherwise
|
|
164
|
+
*/
|
|
165
|
+
function isValidUrl(url: string): boolean {
|
|
166
|
+
try {
|
|
167
|
+
new URL(url);
|
|
168
|
+
return true;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Generate a configuration summary for debugging
|
|
176
|
+
* Hides sensitive information like private keys
|
|
177
|
+
*
|
|
178
|
+
* @param config - Configuration to summarize
|
|
179
|
+
* @returns Safe configuration summary
|
|
180
|
+
*/
|
|
181
|
+
export function getConfigSummary(config: DubheConfig): object {
|
|
182
|
+
return {
|
|
183
|
+
network: config.network,
|
|
184
|
+
packageId: config.packageId,
|
|
185
|
+
dubheSchemaId: config.dubheSchemaId,
|
|
186
|
+
hasMetadata: !!config.metadata,
|
|
187
|
+
hasDubheMetadata: !!config.dubheMetadata,
|
|
188
|
+
hasCredentials: !!config.credentials?.secretKey,
|
|
189
|
+
endpoints: config.endpoints,
|
|
190
|
+
options: config.options
|
|
191
|
+
};
|
|
192
|
+
}
|