frontend-hamroun 1.2.75 → 1.2.77

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.
Files changed (113) hide show
  1. package/dist/batch/package.json +16 -0
  2. package/dist/client-router/package.json +16 -0
  3. package/dist/component/package.json +16 -0
  4. package/dist/context/package.json +16 -0
  5. package/dist/event-bus/package.json +16 -0
  6. package/dist/forms/package.json +16 -0
  7. package/dist/hooks/package.json +16 -0
  8. package/dist/jsx-runtime/package.json +16 -0
  9. package/dist/lifecycle-events/package.json +16 -0
  10. package/dist/package.json +71 -0
  11. package/dist/render-component/package.json +16 -0
  12. package/dist/renderer/package.json +16 -0
  13. package/dist/router/package.json +16 -0
  14. package/dist/server/package.json +17 -0
  15. package/dist/server/src/client-router.d.ts +60 -0
  16. package/dist/server/src/client-router.js +210 -0
  17. package/dist/server/src/client-router.js.map +1 -0
  18. package/dist/server/src/component.js +1 -1
  19. package/dist/server/src/event-bus.d.ts +23 -0
  20. package/dist/server/src/event-bus.js +75 -0
  21. package/dist/server/src/event-bus.js.map +1 -0
  22. package/dist/server/src/forms.d.ts +40 -0
  23. package/dist/server/src/forms.js +148 -0
  24. package/dist/server/src/forms.js.map +1 -0
  25. package/dist/server/src/hooks.js +2 -2
  26. package/dist/server/src/index.js +19 -11
  27. package/dist/server/src/lifecycle-events.d.ts +108 -0
  28. package/dist/server/src/lifecycle-events.js +177 -0
  29. package/dist/server/src/lifecycle-events.js.map +1 -0
  30. package/dist/server/src/renderComponent.js +1 -1
  31. package/dist/server/src/renderer.js +3 -3
  32. package/dist/server/src/router.d.ts +55 -0
  33. package/dist/server/src/router.js +166 -0
  34. package/dist/server/src/router.js.map +1 -0
  35. package/dist/server/src/server/index.d.ts +75 -2
  36. package/dist/server/src/server/index.js +224 -8
  37. package/dist/server/src/server/index.js.map +1 -1
  38. package/dist/server/src/server/server.js +1 -1
  39. package/dist/server/src/server/templates.d.ts +28 -0
  40. package/dist/server/src/server/templates.js +204 -0
  41. package/dist/server/src/server/templates.js.map +1 -0
  42. package/dist/server/src/server/utils.d.ts +70 -0
  43. package/dist/server/src/server/utils.js +156 -0
  44. package/dist/server/src/server/utils.js.map +1 -0
  45. package/dist/server/src/server-renderer.js +1 -1
  46. package/dist/server/src/store.d.ts +41 -0
  47. package/dist/server/src/store.js +99 -0
  48. package/dist/server/src/store.js.map +1 -0
  49. package/dist/server/src/utils.d.ts +46 -0
  50. package/dist/server/src/utils.js +144 -0
  51. package/dist/server/src/utils.js.map +1 -0
  52. package/dist/server/tsconfig.server.tsbuildinfo +1 -1
  53. package/dist/server-renderer/package.json +16 -0
  54. package/dist/store/package.json +16 -0
  55. package/dist/types/package.json +16 -0
  56. package/dist/utils/package.json +16 -0
  57. package/dist/vdom/package.json +16 -0
  58. package/dist/wasm/package.json +16 -0
  59. package/package.json +14 -13
  60. package/templates/complete-app/build.js +284 -0
  61. package/templates/complete-app/package.json +40 -0
  62. package/templates/complete-app/public/styles.css +345 -0
  63. package/templates/complete-app/src/api/index.js +31 -0
  64. package/templates/complete-app/src/client.js +93 -0
  65. package/templates/complete-app/src/components/App.js +66 -0
  66. package/templates/complete-app/src/components/Footer.js +19 -0
  67. package/templates/complete-app/src/components/Header.js +38 -0
  68. package/templates/complete-app/src/pages/About.js +59 -0
  69. package/templates/complete-app/src/pages/Home.js +54 -0
  70. package/templates/complete-app/src/pages/WasmDemo.js +136 -0
  71. package/templates/complete-app/src/server.js +186 -0
  72. package/templates/complete-app/src/wasm/build.bat +16 -0
  73. package/templates/complete-app/src/wasm/build.sh +16 -0
  74. package/templates/complete-app/src/wasm/example.go +101 -0
  75. package/templates/fullstack-app/build/main.css +225 -15
  76. package/templates/fullstack-app/build/main.css.map +2 -2
  77. package/templates/fullstack-app/build/main.js +657 -372
  78. package/templates/fullstack-app/build/main.js.map +4 -4
  79. package/templates/fullstack-app/build.ts +3 -4
  80. package/templates/fullstack-app/public/styles.css +222 -15
  81. package/templates/fullstack-app/server.ts +46 -12
  82. package/templates/fullstack-app/src/components/ClientHome.tsx +0 -0
  83. package/templates/fullstack-app/src/components/ErrorBoundary.tsx +36 -0
  84. package/templates/fullstack-app/src/components/Layout.tsx +23 -26
  85. package/templates/fullstack-app/src/components/StateDemo.tsx +207 -0
  86. package/templates/fullstack-app/src/components/UserList.tsx +30 -13
  87. package/templates/fullstack-app/src/data/api.ts +173 -38
  88. package/templates/fullstack-app/src/main.tsx +88 -154
  89. package/templates/fullstack-app/src/middleware.ts +28 -0
  90. package/templates/fullstack-app/src/pages/404.tsx +28 -0
  91. package/templates/fullstack-app/src/pages/[id].tsx +0 -0
  92. package/templates/fullstack-app/src/pages/_app.tsx +11 -0
  93. package/templates/fullstack-app/src/pages/_document.tsx +25 -0
  94. package/templates/fullstack-app/src/pages/_error.tsx +45 -0
  95. package/templates/fullstack-app/src/pages/about.tsx +71 -0
  96. package/templates/fullstack-app/src/pages/api/users/[id].ts +73 -0
  97. package/templates/fullstack-app/src/pages/api/users/index.ts +43 -0
  98. package/templates/fullstack-app/src/pages/index.tsx +97 -20
  99. package/templates/fullstack-app/src/pages/users/[id].tsx +153 -0
  100. package/templates/fullstack-app/src/pages/wasm-demo.tsx +1 -0
  101. package/templates/go/example.go +99 -86
  102. package/templates/go-wasm-app/babel.config.js +8 -2
  103. package/templates/go-wasm-app/build.config.js +62 -0
  104. package/templates/go-wasm-app/build.js +218 -0
  105. package/templates/go-wasm-app/package.json +21 -12
  106. package/templates/go-wasm-app/server.js +59 -510
  107. package/templates/go-wasm-app/src/app.js +173 -0
  108. package/templates/go-wasm-app/vite.config.js +16 -5
  109. package/templates/ssr-template/client.js +54 -26
  110. package/templates/ssr-template/server.js +5 -28
  111. package/templates/ssr-template/vite.config.js +21 -5
  112. package/dist/server/wasm.d.ts +0 -7
  113. package/dist/wasm.d.ts +0 -37
@@ -0,0 +1,207 @@
1
+ import { jsx, useState, useEffect, useMemo } from 'frontend-hamroun';
2
+
3
+ // Define our state types for better type safety
4
+ interface CounterState {
5
+ count: number;
6
+ lastUpdated: string | null;
7
+ history: number[];
8
+ }
9
+
10
+ export default function StateDemo() {
11
+ // More comprehensive state with history tracking
12
+ const [state, setState] = useState<CounterState>({
13
+ count: 0,
14
+ lastUpdated: null,
15
+ history: []
16
+ });
17
+
18
+ // Track UI state separately
19
+ const [isLoading, setIsLoading] = useState(false);
20
+ const [showHistory, setShowHistory] = useState(false);
21
+
22
+ // Check for server-side rendering
23
+ const isSSR = typeof window === 'undefined';
24
+
25
+ // Calculate derived state with useMemo for performance
26
+ const stats = useMemo(() => {
27
+ // Ensure we have a valid history array
28
+ if (isSSR || !state || !state.history || state.history.length === 0) {
29
+ return { avg: 0, max: 0, min: 0 };
30
+ }
31
+
32
+ try {
33
+ const sum = state.history.reduce((a, b) => a + b, 0);
34
+ return {
35
+ avg: parseFloat((sum / state.history.length).toFixed(1)),
36
+ max: Math.max(...state.history),
37
+ min: Math.min(...state.history)
38
+ };
39
+ } catch (error) {
40
+ console.error('Error calculating stats:', error);
41
+ return { avg: 0, max: 0, min: 0 };
42
+ }
43
+ }, [state?.history]);
44
+
45
+ // Handle increment with history tracking
46
+ const increment = () => {
47
+ if (isSSR) return;
48
+
49
+ setIsLoading(true);
50
+
51
+ // Simulate async operation
52
+ setTimeout(() => {
53
+ setState(prevState => ({
54
+ count: prevState.count + 1,
55
+ lastUpdated: new Date().toLocaleTimeString(),
56
+ history: [...(prevState.history || []), prevState.count + 1]
57
+ }));
58
+ setIsLoading(false);
59
+ }, 300);
60
+ };
61
+
62
+ // Handle decrement with bounds checking
63
+ const decrement = () => {
64
+ if (isSSR) return;
65
+
66
+ setIsLoading(true);
67
+
68
+ // Simulate async operation
69
+ setTimeout(() => {
70
+ setState(prevState => ({
71
+ count: Math.max(0, prevState.count - 1),
72
+ lastUpdated: new Date().toLocaleTimeString(),
73
+ history: prevState.count > 0 ? [...(prevState.history || []), prevState.count - 1] : (prevState.history || [])
74
+ }));
75
+ setIsLoading(false);
76
+ }, 300);
77
+ };
78
+
79
+ // Reset counter
80
+ const reset = () => {
81
+ if (isSSR) return;
82
+
83
+ setState({
84
+ count: 0,
85
+ lastUpdated: new Date().toLocaleTimeString(),
86
+ history: []
87
+ });
88
+ };
89
+
90
+ // Toggle history visibility
91
+ const toggleHistory = () => {
92
+ if (isSSR) return;
93
+
94
+ setShowHistory(prev => !prev);
95
+ };
96
+
97
+ // For SSR, return a simpler version of the component
98
+ if (isSSR) {
99
+ return (
100
+ <div className="bg-white shadow-lg rounded-lg p-6 mb-8">
101
+ <h2 className="text-xl font-semibold text-gray-800 mb-4">
102
+ State Management Demo
103
+ </h2>
104
+ <div className="flex items-center justify-between mb-4">
105
+ <button className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg">-</button>
106
+ <span className="text-2xl font-bold mx-4">0</span>
107
+ <button className="px-4 py-2 bg-blue-600 text-white rounded-lg">+</button>
108
+ </div>
109
+ <p className="text-sm text-gray-500">Interactive counter (client-side only)</p>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ // Regular client-side render
115
+ return (
116
+ <div className="bg-white shadow-lg rounded-lg p-6 mb-8">
117
+ <h2 className="text-xl font-semibold text-gray-800 mb-4">
118
+ State Management Demo
119
+ </h2>
120
+
121
+ <div className="mb-6">
122
+ <div className="flex items-center justify-between mb-4">
123
+ <button
124
+ onClick={decrement}
125
+ className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 disabled:opacity-50"
126
+ disabled={state.count === 0 || isLoading}
127
+ >
128
+ {isLoading ? '...' : '-'}
129
+ </button>
130
+
131
+ <span className="text-2xl font-bold mx-4">{state.count}</span>
132
+
133
+ <button
134
+ onClick={increment}
135
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
136
+ disabled={isLoading}
137
+ >
138
+ {isLoading ? '...' : '+'}
139
+ </button>
140
+ </div>
141
+
142
+ <button
143
+ onClick={reset}
144
+ className="text-sm text-gray-600 hover:text-red-600"
145
+ disabled={state.count === 0 && (!state.history || state.history.length === 0)}
146
+ >
147
+ Reset Counter
148
+ </button>
149
+ </div>
150
+
151
+ {state.lastUpdated && (
152
+ <div className="mb-4">
153
+ <p className="text-sm text-gray-500">
154
+ Last updated: {state.lastUpdated}
155
+ </p>
156
+ </div>
157
+ )}
158
+
159
+ {state.history && state.history.length > 0 && (
160
+ <div className="border-t pt-4 mt-4">
161
+ <div className="flex items-center justify-between mb-2">
162
+ <h3 className="text-md font-medium text-gray-700">Statistics</h3>
163
+ <button
164
+ onClick={toggleHistory}
165
+ className="text-sm text-blue-600 hover:underline"
166
+ >
167
+ {showHistory ? 'Hide History' : 'Show History'}
168
+ </button>
169
+ </div>
170
+
171
+ <div className="grid grid-cols-3 gap-2 mb-3">
172
+ <div className="bg-blue-50 p-2 rounded text-center">
173
+ <div className="text-xs text-gray-500">Average</div>
174
+ <div className="font-bold">{stats.avg}</div>
175
+ </div>
176
+ <div className="bg-green-50 p-2 rounded text-center">
177
+ <div className="text-xs text-gray-500">Maximum</div>
178
+ <div className="font-bold">{stats.max}</div>
179
+ </div>
180
+ <div className="bg-red-50 p-2 rounded text-center">
181
+ <div className="text-xs text-gray-500">Minimum</div>
182
+ <div className="font-bold">{stats.min}</div>
183
+ </div>
184
+ </div>
185
+
186
+ {showHistory && (
187
+ <div className="mt-3">
188
+ <h4 className="text-sm font-medium text-gray-600 mb-1">
189
+ History ({state.history.length} events)
190
+ </h4>
191
+ <div className="bg-gray-50 p-2 rounded max-h-24 overflow-y-auto text-xs">
192
+ {state.history.map((value, index) => (
193
+ <span
194
+ key={index}
195
+ className="inline-block bg-gray-200 rounded px-2 py-1 m-1"
196
+ >
197
+ {value}
198
+ </span>
199
+ ))}
200
+ </div>
201
+ </div>
202
+ )}
203
+ </div>
204
+ )}
205
+ </div>
206
+ );
207
+ }
@@ -1,27 +1,44 @@
1
1
  import { jsx } from 'frontend-hamroun';
2
2
 
3
- const UserList = ({ users }) => {
3
+ interface User {
4
+ id: number;
5
+ name: string;
6
+ email: string;
7
+ }
8
+
9
+ interface UserListProps {
10
+ users: User[];
11
+ }
12
+
13
+ export default function UserList({ users }: UserListProps) {
4
14
  if (!users || users.length === 0) {
5
15
  return (
6
- <div className="p-4 bg-gray-50 rounded-lg border border-gray-200 my-4">
7
- <p className="text-gray-500 italic">No users found or still loading...</p>
16
+ <div className="empty-state p-4 text-center bg-gray-50 rounded">
17
+ <p className="text-gray-500">No users available</p>
8
18
  </div>
9
19
  );
10
20
  }
11
-
21
+
12
22
  return (
13
- <div className="rounded-lg overflow-hidden my-4">
14
- <h3 className="text-lg font-medium mb-3">Users ({users.length})</h3>
15
- <ul className="divide-y divide-gray-200 border border-gray-200 rounded-lg overflow-hidden">
23
+ <div className="user-list">
24
+ <ul className="divide-y divide-gray-100">
16
25
  {users.map(user => (
17
- <li key={user.id} className="flex justify-between items-center p-4 hover:bg-gray-50">
18
- <span className="font-medium text-gray-800">{user.name}</span>
19
- <span className="text-gray-500 text-sm">{user.email}</span>
26
+ <li key={user.id} className="py-3">
27
+ <div className="flex justify-between">
28
+ <div>
29
+ <p className="font-medium text-gray-900">{user.name}</p>
30
+ <p className="text-sm text-gray-500">{user.email}</p>
31
+ </div>
32
+ <a
33
+ href={`/users/${user.id}`}
34
+ className="text-blue-600 hover:underline text-sm self-center"
35
+ >
36
+ View Profile
37
+ </a>
38
+ </div>
20
39
  </li>
21
40
  ))}
22
41
  </ul>
23
42
  </div>
24
43
  );
25
- };
26
-
27
- export default UserList;
44
+ }
@@ -1,22 +1,42 @@
1
- // General API utility for data fetching
1
+ /**
2
+ * API utilities for making requests to the backend
3
+ */
4
+ import { batchUpdates } from 'frontend-hamroun';
5
+
6
+ // Cache for API responses
7
+ const apiCache = new Map<string, { data: any, timestamp: number }>();
8
+ const CACHE_DURATION = 60000; // 1 minute cache
2
9
 
3
10
  /**
4
- * Fetch data from an API endpoint
5
- * @param endpoint The API endpoint to fetch from (without /api prefix)
6
- * @param options Additional fetch options
7
- * @returns The parsed JSON response or null if there was an error
11
+ * Fetch data from the API with caching
8
12
  */
9
- export async function fetchApi(endpoint: string, options = {}) {
13
+ export async function fetchApi(
14
+ endpoint: string,
15
+ options: RequestInit & {
16
+ useCache?: boolean,
17
+ forceFresh?: boolean
18
+ } = {}
19
+ ): Promise<any> {
10
20
  const url = endpoint.startsWith('/') ? `/api${endpoint}` : `/api/${endpoint}`;
21
+ const { useCache = true, forceFresh = false, ...fetchOptions } = options;
22
+
23
+ // Check cache first if enabled
24
+ if (useCache && !forceFresh) {
25
+ const cached = apiCache.get(url);
26
+ if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
27
+ console.log(`[API] Using cached data for: ${url}`);
28
+ return cached.data;
29
+ }
30
+ }
11
31
 
12
32
  try {
13
33
  console.log(`[API] Fetching data from: ${url}`);
14
34
  const response = await fetch(url, {
15
35
  headers: {
16
36
  'Content-Type': 'application/json',
17
- 'Accept': 'application/json'
37
+ 'Accept': 'application/json',
18
38
  },
19
- ...options
39
+ ...fetchOptions
20
40
  });
21
41
 
22
42
  if (!response.ok) {
@@ -25,47 +45,162 @@ export async function fetchApi(endpoint: string, options = {}) {
25
45
 
26
46
  const data = await response.json();
27
47
  console.log(`[API] Successfully fetched data from: ${url}`);
48
+
49
+ // Cache the response if caching is enabled
50
+ if (useCache) {
51
+ apiCache.set(url, { data, timestamp: Date.now() });
52
+ }
53
+
28
54
  return data;
29
55
  } catch (error) {
30
56
  console.error(`[API] Error fetching from ${url}:`, error);
31
- return null;
57
+ throw error;
32
58
  }
33
59
  }
34
60
 
35
61
  /**
36
- * User-related API calls
62
+ * Clear the API cache
37
63
  */
64
+ export function clearApiCache(endpoint?: string): void {
65
+ if (endpoint) {
66
+ const url = endpoint.startsWith('/') ? `/api${endpoint}` : `/api/${endpoint}`;
67
+ apiCache.delete(url);
68
+ } else {
69
+ apiCache.clear();
70
+ }
71
+ }
72
+
73
+ // Sample data for development
74
+ const sampleUsers = [
75
+ { id: 1, name: 'User 1', email: 'user1@example.com' },
76
+ { id: 2, name: 'User 2', email: 'user2@example.com' },
77
+ { id: 3, name: 'User 3', email: 'user3@example.com' }
78
+ ];
79
+
80
+ const samplePosts = [
81
+ { id: 1, title: 'Post 1', content: 'Content for post 1', authorId: 1 },
82
+ { id: 2, title: 'Post 2', content: 'Content for post 2', authorId: 2 },
83
+ { id: 3, title: 'Post 3', content: 'Content for post 3', authorId: 1 }
84
+ ];
85
+
86
+ // Helper to simulate network delay
87
+ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
88
+
89
+ // User API client
38
90
  export const UserApi = {
39
- getAll: () => fetchApi('/users'),
40
- getById: (id: number | string) => fetchApi(`/users/${id}`),
41
- create: (data: any) => fetchApi('/users', {
42
- method: 'POST',
43
- body: JSON.stringify(data)
44
- }),
45
- update: (id: number | string, data: any) => fetchApi(`/users/${id}`, {
46
- method: 'PUT',
47
- body: JSON.stringify(data)
48
- }),
49
- delete: (id: number | string) => fetchApi(`/users/${id}`, {
50
- method: 'DELETE'
51
- })
91
+ // Get all users
92
+ async getAll() {
93
+ try {
94
+ // In a real app, we'd fetch from API
95
+ // const response = await fetch('/api/users');
96
+ // if (!response.ok) throw new Error('Failed to fetch users');
97
+ // return await response.json();
98
+
99
+ // Simulate API delay
100
+ await delay(300);
101
+ return [...sampleUsers]; // Return a copy to avoid mutations
102
+ } catch (error) {
103
+ console.error('Error fetching users:', error);
104
+ return [];
105
+ }
106
+ },
107
+
108
+ // Get user by ID
109
+ async getById(id: number | string) {
110
+ try {
111
+ // In a real app, we'd fetch from API
112
+ // const response = await fetch(`/api/users/${id}`);
113
+ // if (!response.ok) throw new Error('User not found');
114
+ // return await response.json();
115
+
116
+ // Convert ID to number if it's a string
117
+ const userId = typeof id === 'string' ? parseInt(id, 10) : id;
118
+
119
+ // Simulate API delay
120
+ await delay(200);
121
+
122
+ // Find user
123
+ const user = sampleUsers.find(u => u.id === userId);
124
+ if (!user) throw new Error('User not found');
125
+
126
+ return { ...user }; // Return a copy to avoid mutations
127
+ } catch (error) {
128
+ console.error(`Error fetching user ${id}:`, error);
129
+ return null;
130
+ }
131
+ },
132
+
133
+ // Get posts (all or by author)
134
+ async getPosts(authorId?: number | string) {
135
+ try {
136
+ // In a real app, we'd fetch from API
137
+ // const url = authorId ? `/api/posts?authorId=${authorId}` : '/api/posts';
138
+ // const response = await fetch(url);
139
+ // if (!response.ok) throw new Error('Failed to fetch posts');
140
+ // return await response.json();
141
+
142
+ // Convert authorId to number if it's a string
143
+ const userId = authorId ? (typeof authorId === 'string' ? parseInt(authorId, 10) : authorId) : undefined;
144
+
145
+ // Simulate API delay
146
+ await delay(400);
147
+
148
+ // Filter posts if authorId is provided
149
+ const posts = userId
150
+ ? samplePosts.filter(p => p.authorId === userId)
151
+ : samplePosts;
152
+
153
+ return [...posts]; // Return a copy to avoid mutations
154
+ } catch (error) {
155
+ console.error('Error fetching posts:', error);
156
+ return [];
157
+ }
158
+ }
52
159
  };
53
160
 
54
- /**
55
- * Post-related API calls
56
- */
57
- export const PostApi = {
58
- getAll: () => fetchApi('/posts'),
59
- getById: (id: number | string) => fetchApi(`/posts/${id}`),
60
- create: (data: any) => fetchApi('/posts', {
161
+ // WASM API service
162
+ export const WasmApi = {
163
+ calculate: (operation: 'add' | 'multiply', a: number, b: number) =>
164
+ fetchApi(`/wasm/calculate?op=${operation}&a=${a}&b=${b}`),
165
+
166
+ processJson: (data: any) => fetchApi('/wasm/process-json', {
61
167
  method: 'POST',
62
- body: JSON.stringify(data)
63
- }),
64
- update: (id: number | string, data: any) => fetchApi(`/posts/${id}`, {
65
- method: 'PUT',
66
- body: JSON.stringify(data)
67
- }),
68
- delete: (id: number | string) => fetchApi(`/posts/${id}`, {
69
- method: 'DELETE'
168
+ body: JSON.stringify(data),
169
+ useCache: false
70
170
  })
71
171
  };
172
+
173
+ // Batch multiple API calls for efficiency
174
+ export const batchApiCalls = async <T>(
175
+ apiFunctions: Array<() => Promise<T>>
176
+ ): Promise<T[]> => {
177
+ const results: T[] = [];
178
+ let errors: Error[] = [];
179
+
180
+ // Execute all API calls in parallel
181
+ const promises = apiFunctions.map(fn => fn());
182
+
183
+ try {
184
+ const settledResults = await Promise.allSettled(promises);
185
+
186
+ // Process results
187
+ batchUpdates(() => {
188
+ settledResults.forEach(result => {
189
+ if (result.status === 'fulfilled') {
190
+ results.push(result.value);
191
+ } else {
192
+ errors.push(result.reason);
193
+ }
194
+ });
195
+ });
196
+
197
+ if (errors.length) {
198
+ console.error('[API] Some batch API calls failed:', errors);
199
+ }
200
+
201
+ return results;
202
+ } catch (error) {
203
+ console.error('[API] Batch API calls failed:', error);
204
+ throw error;
205
+ }
206
+ };