This repository has no description
7.3 kB
261 lines
1import type { GetGraphDataResponse, GraphNode, GraphEdge } from '@semble/types';
2
3/**
4 * Configuration for mock graph data generation
5 */
6interface MockGraphConfig {
7 nodeCount: number;
8 edgeDensity: number; // 0-1, percentage of possible edges to create
9 typeDistribution?: {
10 USER?: number;
11 URL?: number;
12 COLLECTION?: number;
13 NOTE?: number;
14 };
15}
16
17/**
18 * Generate random mock graph data for performance testing
19 *
20 * @param config Configuration for data generation
21 * @returns Mock graph data with nodes and edges
22 *
23 * @example
24 * // Generate 1000 nodes with moderate edge density
25 * const mockData = generateMockGraphData({ nodeCount: 1000, edgeDensity: 0.01 });
26 *
27 * // Generate 5000 nodes with custom type distribution
28 * const mockData = generateMockGraphData({
29 * nodeCount: 5000,
30 * edgeDensity: 0.005,
31 * typeDistribution: { USER: 0.2, URL: 0.5, COLLECTION: 0.2, NOTE: 0.1 }
32 * });
33 */
34export function generateMockGraphData(
35 config: MockGraphConfig,
36): GetGraphDataResponse {
37 const {
38 nodeCount,
39 edgeDensity,
40 typeDistribution = { USER: 0.25, URL: 0.4, COLLECTION: 0.2, NOTE: 0.15 },
41 } = config;
42
43 // Generate nodes
44 const nodes: GraphNode[] = [];
45 const nodeTypes: Array<GraphNode['type']> = [
46 'USER',
47 'URL',
48 'COLLECTION',
49 'NOTE',
50 ];
51
52 for (let i = 0; i < nodeCount; i++) {
53 // Determine node type based on distribution
54 const rand = Math.random();
55 let cumulativeProb = 0;
56 let type: GraphNode['type'] = 'URL';
57
58 for (const [nodeType, prob] of Object.entries(typeDistribution)) {
59 cumulativeProb += prob;
60 if (rand <= cumulativeProb) {
61 type = nodeType as GraphNode['type'];
62 break;
63 }
64 }
65
66 nodes.push(generateNode(i, type));
67 }
68
69 // Generate edges
70 const edges: GraphEdge[] = [];
71 const edgeTypes: Array<GraphEdge['type']> = [
72 'USER_FOLLOWS_USER',
73 'USER_FOLLOWS_COLLECTION',
74 'USER_AUTHORED_URL',
75 'NOTE_REFERENCES_URL',
76 'COLLECTION_CONTAINS_URL',
77 'URL_CONNECTS_URL',
78 ];
79
80 // Calculate how many edges to create
81 const maxPossibleEdges = nodeCount * (nodeCount - 1);
82 const targetEdgeCount = Math.floor(maxPossibleEdges * edgeDensity);
83
84 let edgeId = 0;
85 for (let i = 0; i < targetEdgeCount; i++) {
86 // Pick two random nodes
87 const sourceIdx = Math.floor(Math.random() * nodeCount);
88 let targetIdx = Math.floor(Math.random() * nodeCount);
89
90 // Ensure source and target are different
91 while (targetIdx === sourceIdx) {
92 targetIdx = Math.floor(Math.random() * nodeCount);
93 }
94
95 const sourceNode = nodes[sourceIdx];
96 const targetNode = nodes[targetIdx];
97
98 // Determine appropriate edge type based on node types
99 const edgeType = getAppropriateEdgeType(sourceNode.type, targetNode.type);
100 if (!edgeType) continue; // Skip if no valid edge type for this combination
101
102 edges.push({
103 id: `edge-${edgeId++}`,
104 source: sourceNode.id,
105 target: targetNode.id,
106 type: edgeType,
107 metadata: {},
108 });
109 }
110
111 console.log(
112 `Generated mock graph data: ${nodes.length} nodes, ${edges.length} edges`,
113 );
114
115 // Return with pagination metadata (mock assumes single page with all data)
116 return {
117 nodes,
118 edges,
119 pagination: {
120 currentPage: 1,
121 totalPages: 1,
122 totalCount: nodes.length,
123 hasMore: false,
124 limit: nodes.length,
125 },
126 };
127}
128
129/**
130 * Generate a single node with realistic mock data
131 */
132function generateNode(index: number, type: GraphNode['type']): GraphNode {
133 const id = `${type.toLowerCase()}-${index}`;
134
135 switch (type) {
136 case 'USER':
137 return {
138 id,
139 type,
140 label: `User ${index}`,
141 metadata: {
142 handle: `user${index}.bsky.social`,
143 name: `User ${index}`,
144 avatarUrl: `https://picsum.photos/seed/${index}/200/200`,
145 description: `This is a mock user profile for testing. User ${index}.`,
146 followerCount: Math.floor(Math.random() * 1000),
147 followingCount: Math.floor(Math.random() * 500),
148 },
149 };
150
151 case 'URL':
152 const urlTypes = ['article', 'video', 'book', 'research', 'link'];
153 const urlType = urlTypes[Math.floor(Math.random() * urlTypes.length)];
154 return {
155 id,
156 type,
157 label: `${urlType.charAt(0).toUpperCase() + urlType.slice(1)} ${index}`,
158 metadata: {
159 url: `https://example.com/${urlType}/${index}`,
160 title: `Interesting ${urlType} about topic ${index}`,
161 description: `This is a mock ${urlType} for performance testing. Content ${index}.`,
162 author: `Author ${Math.floor(Math.random() * 100)}`,
163 siteName: 'Example.com',
164 libraryCount: Math.floor(Math.random() * 50),
165 urlType,
166 },
167 };
168
169 case 'COLLECTION':
170 const accessTypes = ['OPEN', 'CLOSED'];
171 const accessType =
172 accessTypes[Math.floor(Math.random() * accessTypes.length)];
173 return {
174 id,
175 type,
176 label: `Collection ${index}`,
177 metadata: {
178 name: `Collection ${index}`,
179 description: `A curated collection of resources about topic ${index}`,
180 handle: `user${Math.floor(Math.random() * 50)}.bsky.social`,
181 rkey: `collection${index}`,
182 authorName: `User ${Math.floor(Math.random() * 50)}`,
183 cardCount: Math.floor(Math.random() * 100),
184 followerCount: Math.floor(Math.random() * 200),
185 accessType,
186 },
187 };
188
189 case 'NOTE':
190 return {
191 id,
192 type,
193 label: `Note ${index}`,
194 metadata: {
195 text: `This is a note with thoughts and insights about resource ${index}. Mock content for testing.`,
196 authorName: `User ${Math.floor(Math.random() * 50)}`,
197 parentUrl: `https://example.com/article/${Math.floor(Math.random() * 1000)}`,
198 },
199 };
200
201 default:
202 return {
203 id,
204 type: 'URL',
205 label: `Node ${index}`,
206 metadata: {},
207 };
208 }
209}
210
211/**
212 * Determine appropriate edge type based on source and target node types
213 */
214function getAppropriateEdgeType(
215 sourceType: GraphNode['type'],
216 targetType: GraphNode['type'],
217): GraphEdge['type'] | null {
218 // Define valid edge type combinations
219 const validCombinations: Record<string, GraphEdge['type']> = {
220 'USER-USER': 'USER_FOLLOWS_USER',
221 'USER-COLLECTION': 'USER_FOLLOWS_COLLECTION',
222 'USER-URL': 'USER_AUTHORED_URL',
223 'NOTE-URL': 'NOTE_REFERENCES_URL',
224 'COLLECTION-URL': 'COLLECTION_CONTAINS_URL',
225 'URL-URL': 'URL_CONNECTS_URL',
226 };
227
228 const key = `${sourceType}-${targetType}`;
229 return validCombinations[key] || null;
230}
231
232/**
233 * Preset configurations for common testing scenarios
234 */
235export const MOCK_GRAPH_PRESETS = {
236 // Small graph for quick testing
237 small: {
238 nodeCount: 100,
239 edgeDensity: 0.02,
240 },
241 // Medium graph for moderate testing
242 medium: {
243 nodeCount: 500,
244 edgeDensity: 0.01,
245 },
246 // Large graph for performance testing
247 large: {
248 nodeCount: 2000,
249 edgeDensity: 0.005,
250 },
251 // Extra large graph for stress testing
252 extraLarge: {
253 nodeCount: 5000,
254 edgeDensity: 0.002,
255 },
256 // Dense small graph for complex visualization
257 denseSmall: {
258 nodeCount: 200,
259 edgeDensity: 0.05,
260 },
261} as const;