juxscript 1.1.4 → 1.1.6

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 (205) hide show
  1. package/index.d.ts +10 -10
  2. package/index.d.ts.map +1 -0
  3. package/lib/components/alert.d.ts +32 -0
  4. package/lib/components/alert.d.ts.map +1 -0
  5. package/lib/components/alert.js +153 -0
  6. package/lib/components/alert.ts +200 -0
  7. package/lib/components/app.d.ts +89 -0
  8. package/lib/components/app.d.ts.map +1 -0
  9. package/lib/components/app.js +175 -0
  10. package/lib/components/app.ts +247 -0
  11. package/lib/components/badge.d.ts +27 -0
  12. package/lib/components/badge.d.ts.map +1 -0
  13. package/lib/components/badge.js +70 -0
  14. package/lib/components/badge.ts +101 -0
  15. package/lib/components/base/BaseComponent.d.ts +142 -0
  16. package/lib/components/base/BaseComponent.d.ts.map +1 -0
  17. package/lib/components/base/BaseComponent.js +363 -0
  18. package/lib/components/base/BaseComponent.ts +421 -0
  19. package/lib/components/base/FormInput.d.ts +73 -0
  20. package/lib/components/base/FormInput.d.ts.map +1 -0
  21. package/lib/components/base/FormInput.js +163 -0
  22. package/lib/components/base/FormInput.ts +227 -0
  23. package/lib/components/button.d.ts +48 -0
  24. package/lib/components/button.d.ts.map +1 -0
  25. package/lib/components/button.js +121 -0
  26. package/lib/components/button.ts +178 -0
  27. package/lib/components/card.d.ts +34 -0
  28. package/lib/components/card.d.ts.map +1 -0
  29. package/lib/components/card.js +127 -0
  30. package/lib/components/card.ts +173 -0
  31. package/lib/components/chart.d.ts +45 -0
  32. package/lib/components/chart.d.ts.map +1 -0
  33. package/lib/components/chart.js +186 -0
  34. package/lib/components/chart.ts +231 -0
  35. package/lib/components/checkbox.d.ts +31 -0
  36. package/lib/components/checkbox.d.ts.map +1 -0
  37. package/lib/components/checkbox.js +185 -0
  38. package/lib/components/checkbox.ts +242 -0
  39. package/lib/components/code.d.ts +24 -0
  40. package/lib/components/code.d.ts.map +1 -0
  41. package/lib/components/code.js +88 -0
  42. package/lib/components/code.ts +123 -0
  43. package/lib/components/container.d.ts +42 -0
  44. package/lib/components/container.d.ts.map +1 -0
  45. package/lib/components/container.js +93 -0
  46. package/lib/components/container.ts +140 -0
  47. package/lib/components/data.d.ts +36 -0
  48. package/lib/components/data.d.ts.map +1 -0
  49. package/lib/components/data.js +110 -0
  50. package/lib/components/data.ts +135 -0
  51. package/lib/components/datepicker.d.ts +38 -0
  52. package/lib/components/datepicker.d.ts.map +1 -0
  53. package/lib/components/datepicker.js +177 -0
  54. package/lib/components/datepicker.ts +234 -0
  55. package/lib/components/dialog.d.ts +38 -0
  56. package/lib/components/dialog.d.ts.map +1 -0
  57. package/lib/components/dialog.js +126 -0
  58. package/lib/components/dialog.ts +172 -0
  59. package/lib/components/divider.d.ts +30 -0
  60. package/lib/components/divider.d.ts.map +1 -0
  61. package/lib/components/divider.js +69 -0
  62. package/lib/components/divider.ts +100 -0
  63. package/lib/components/dropdown.d.ts +39 -0
  64. package/lib/components/dropdown.d.ts.map +1 -0
  65. package/lib/components/dropdown.js +133 -0
  66. package/lib/components/dropdown.ts +186 -0
  67. package/lib/components/element.d.ts +50 -0
  68. package/lib/components/element.d.ts.map +1 -0
  69. package/lib/components/element.js +206 -0
  70. package/lib/components/element.ts +267 -0
  71. package/lib/components/fileupload.d.ts +40 -0
  72. package/lib/components/fileupload.d.ts.map +1 -0
  73. package/lib/components/fileupload.js +241 -0
  74. package/lib/components/fileupload.ts +309 -0
  75. package/lib/components/grid.d.ts +87 -0
  76. package/lib/components/grid.d.ts.map +1 -0
  77. package/lib/components/grid.js +205 -0
  78. package/lib/components/grid.ts +291 -0
  79. package/lib/components/guard.d.ts +41 -0
  80. package/lib/components/guard.d.ts.map +1 -0
  81. package/lib/components/guard.js +56 -0
  82. package/lib/components/guard.ts +92 -0
  83. package/lib/components/heading.d.ts +24 -0
  84. package/lib/components/heading.d.ts.map +1 -0
  85. package/lib/components/heading.js +67 -0
  86. package/lib/components/heading.ts +96 -0
  87. package/lib/components/helpers.d.ts +9 -0
  88. package/lib/components/helpers.d.ts.map +1 -0
  89. package/lib/components/helpers.js +30 -0
  90. package/lib/components/helpers.ts +41 -0
  91. package/lib/components/hero.d.ts +45 -0
  92. package/lib/components/hero.d.ts.map +1 -0
  93. package/lib/components/hero.js +165 -0
  94. package/lib/components/hero.ts +224 -0
  95. package/lib/components/icon.d.ts +35 -0
  96. package/lib/components/icon.d.ts.map +1 -0
  97. package/lib/components/icon.js +132 -0
  98. package/lib/components/icon.ts +178 -0
  99. package/lib/components/icons.d.ts +25 -0
  100. package/lib/components/icons.d.ts.map +1 -0
  101. package/lib/components/icons.js +440 -0
  102. package/lib/components/icons.ts +464 -0
  103. package/lib/components/include.d.ts +120 -0
  104. package/lib/components/include.d.ts.map +1 -0
  105. package/lib/components/include.js +350 -0
  106. package/lib/components/include.ts +410 -0
  107. package/lib/components/input.d.ts +83 -0
  108. package/lib/components/input.d.ts.map +1 -0
  109. package/lib/components/input.js +348 -0
  110. package/lib/components/input.ts +457 -0
  111. package/lib/components/list.d.ts +82 -0
  112. package/lib/components/list.d.ts.map +1 -0
  113. package/lib/components/list.js +311 -0
  114. package/lib/components/list.ts +419 -0
  115. package/lib/components/loading.d.ts +24 -0
  116. package/lib/components/loading.d.ts.map +1 -0
  117. package/lib/components/loading.js +73 -0
  118. package/lib/components/loading.ts +100 -0
  119. package/lib/components/menu.d.ts +37 -0
  120. package/lib/components/menu.d.ts.map +1 -0
  121. package/lib/components/menu.js +202 -0
  122. package/lib/components/menu.ts +275 -0
  123. package/lib/components/modal.d.ts +51 -0
  124. package/lib/components/modal.d.ts.map +1 -0
  125. package/lib/components/modal.js +227 -0
  126. package/lib/components/modal.ts +284 -0
  127. package/lib/components/nav.d.ts +45 -0
  128. package/lib/components/nav.d.ts.map +1 -0
  129. package/lib/components/nav.js +190 -0
  130. package/lib/components/nav.ts +257 -0
  131. package/lib/components/paragraph.d.ts +21 -0
  132. package/lib/components/paragraph.d.ts.map +1 -0
  133. package/lib/components/paragraph.js +70 -0
  134. package/lib/components/paragraph.ts +97 -0
  135. package/lib/components/progress.d.ts +39 -0
  136. package/lib/components/progress.d.ts.map +1 -0
  137. package/lib/components/progress.js +113 -0
  138. package/lib/components/progress.ts +159 -0
  139. package/lib/components/radio.d.ts +41 -0
  140. package/lib/components/radio.d.ts.map +1 -0
  141. package/lib/components/radio.js +203 -0
  142. package/lib/components/radio.ts +278 -0
  143. package/lib/components/req.d.ts +155 -0
  144. package/lib/components/req.d.ts.map +1 -0
  145. package/lib/components/req.js +253 -0
  146. package/lib/components/req.ts +303 -0
  147. package/lib/components/script.d.ts +14 -0
  148. package/lib/components/script.d.ts.map +1 -0
  149. package/lib/components/script.js +33 -0
  150. package/lib/components/script.ts +41 -0
  151. package/lib/components/select.d.ts +40 -0
  152. package/lib/components/select.d.ts.map +1 -0
  153. package/lib/components/select.js +183 -0
  154. package/lib/components/select.ts +252 -0
  155. package/lib/components/sidebar.d.ts +48 -0
  156. package/lib/components/sidebar.d.ts.map +1 -0
  157. package/lib/components/sidebar.js +207 -0
  158. package/lib/components/sidebar.ts +275 -0
  159. package/lib/components/style.d.ts +14 -0
  160. package/lib/components/style.d.ts.map +1 -0
  161. package/lib/components/style.js +33 -0
  162. package/lib/components/style.ts +41 -0
  163. package/lib/components/switch.d.ts +32 -0
  164. package/lib/components/switch.d.ts.map +1 -0
  165. package/lib/components/switch.js +186 -0
  166. package/lib/components/switch.ts +246 -0
  167. package/lib/components/table.d.ts +137 -0
  168. package/lib/components/table.d.ts.map +1 -0
  169. package/lib/components/table.js +1045 -0
  170. package/lib/components/table.ts +1249 -0
  171. package/lib/components/tabs.d.ts +36 -0
  172. package/lib/components/tabs.d.ts.map +1 -0
  173. package/lib/components/tabs.js +198 -0
  174. package/lib/components/tabs.ts +250 -0
  175. package/lib/components/theme-toggle.d.ts +44 -0
  176. package/lib/components/theme-toggle.d.ts.map +1 -0
  177. package/lib/components/theme-toggle.js +215 -0
  178. package/lib/components/theme-toggle.ts +293 -0
  179. package/lib/components/tooltip.d.ts +30 -0
  180. package/lib/components/tooltip.d.ts.map +1 -0
  181. package/lib/components/tooltip.js +109 -0
  182. package/lib/components/tooltip.ts +144 -0
  183. package/lib/components/view.d.ts +48 -0
  184. package/lib/components/view.d.ts.map +1 -0
  185. package/lib/components/view.js +149 -0
  186. package/lib/components/view.ts +190 -0
  187. package/lib/components/write.d.ts +107 -0
  188. package/lib/components/write.d.ts.map +1 -0
  189. package/lib/components/write.js +222 -0
  190. package/lib/components/write.ts +272 -0
  191. package/lib/layouts/default.css +260 -0
  192. package/lib/layouts/figma.css +334 -0
  193. package/lib/reactivity/state.d.ts +36 -0
  194. package/lib/reactivity/state.d.ts.map +1 -0
  195. package/lib/reactivity/state.js +67 -0
  196. package/lib/reactivity/state.ts +78 -0
  197. package/lib/utils/fetch.d.ts +176 -0
  198. package/lib/utils/fetch.d.ts.map +1 -0
  199. package/lib/utils/fetch.js +427 -0
  200. package/lib/utils/fetch.ts +553 -0
  201. package/machinery/compiler3.js +78 -0
  202. package/machinery/doc-generator.js +136 -0
  203. package/machinery/imports.js +155 -0
  204. package/machinery/ts-shim.js +46 -0
  205. package/package.json +9 -15
@@ -0,0 +1,553 @@
1
+ /**
2
+ * Jux Fetch Utility
3
+ *
4
+ * A lightweight fetch wrapper with sensible defaults, error handling, and method chaining.
5
+ * Includes configuration helpers for juxconfig.js integration.
6
+ *
7
+ * Usage:
8
+ * // Configure once (optional)
9
+ * jux.fetch.config({
10
+ * baseUrl: 'https://api.example.com',
11
+ * credentials: 'include',
12
+ * timeout: 10000,
13
+ * log: 'errors',
14
+ * onUnauthorized: () => window.location.href = '/login'
15
+ * });
16
+ *
17
+ * // Simple GET
18
+ * const { data, error } = await jux.fetch('/users').send();
19
+ *
20
+ * // Method chaining
21
+ * const { data, error } = await jux.fetch('/users')
22
+ * .method('POST')
23
+ * .body({ name: 'John' })
24
+ * .params({ limit: 10 })
25
+ * .timeout(5000)
26
+ * .log(true)
27
+ * .send();
28
+ *
29
+ * // With juxconfig services
30
+ * const { data } = await jux.fetch('/users')
31
+ * .baseUrl(jux.fetch.getServiceUrl('database'))
32
+ * .send();
33
+ *
34
+ * // Service client helper
35
+ * const db = jux.fetch.serviceClient('database');
36
+ * const { data } = await db.fetch('/users').send();
37
+ */
38
+
39
+ export type LogLevel = boolean | 'errors';
40
+
41
+ export interface FetchConfig {
42
+ baseUrl?: string;
43
+ credentials?: RequestCredentials;
44
+ headers?: Record<string, string>;
45
+ timeout?: number;
46
+ log?: LogLevel;
47
+ onUnauthorized?: () => void;
48
+ onError?: (error: FetchError) => void;
49
+ }
50
+
51
+ export interface FetchOptions extends Omit<RequestInit, 'body'> {
52
+ params?: Record<string, any>;
53
+ body?: any;
54
+ timeout?: number;
55
+ log?: LogLevel;
56
+ onUnauthorized?: () => void;
57
+ onError?: (error: FetchError) => void;
58
+ parseResponse?: boolean;
59
+ }
60
+
61
+ export interface FetchError {
62
+ message: string;
63
+ status?: number;
64
+ statusText?: string;
65
+ data?: any;
66
+ }
67
+
68
+ export interface FetchResult<T = any> {
69
+ data: T | null;
70
+ error: FetchError | null;
71
+ status: number;
72
+ response: Response;
73
+ }
74
+
75
+ // Global configuration
76
+ let globalConfig: FetchConfig = {
77
+ credentials: 'same-origin',
78
+ headers: {
79
+ 'Content-Type': 'application/json'
80
+ },
81
+ timeout: 30000,
82
+ log: false
83
+ };
84
+
85
+ /**
86
+ * Configure global fetch defaults
87
+ */
88
+ export function configureFetch(config: FetchConfig): void {
89
+ globalConfig = {
90
+ ...globalConfig,
91
+ ...config,
92
+ headers: {
93
+ ...globalConfig.headers,
94
+ ...config.headers
95
+ }
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Build URL with query parameters
101
+ */
102
+ function buildUrl(url: string, params?: Record<string, any>): string {
103
+ if (!params || Object.keys(params).length === 0) {
104
+ return url;
105
+ }
106
+
107
+ const searchParams = new URLSearchParams();
108
+ Object.entries(params).forEach(([key, value]) => {
109
+ if (value !== undefined && value !== null) {
110
+ searchParams.append(key, String(value));
111
+ }
112
+ });
113
+
114
+ const queryString = searchParams.toString();
115
+ if (!queryString) return url;
116
+
117
+ return url.includes('?')
118
+ ? `${url}&${queryString}`
119
+ : `${url}?${queryString}`;
120
+ }
121
+
122
+ /**
123
+ * Main fetch function
124
+ */
125
+ async function executeFetch<T = any>(
126
+ url: string,
127
+ options: FetchOptions = {}
128
+ ): Promise<FetchResult<T>> {
129
+ const {
130
+ params,
131
+ body,
132
+ timeout = globalConfig.timeout,
133
+ log = globalConfig.log,
134
+ onUnauthorized = globalConfig.onUnauthorized,
135
+ onError = globalConfig.onError,
136
+ parseResponse = true,
137
+ headers = {},
138
+ ...fetchOptions
139
+ } = options;
140
+
141
+ // Build full URL
142
+ let fullUrl = url;
143
+
144
+ // Add base URL if configured and URL is relative
145
+ if (globalConfig.baseUrl && !url.startsWith('http://') && !url.startsWith('https://')) {
146
+ fullUrl = `${globalConfig.baseUrl}${url.startsWith('/') ? url : `/${url}`}`;
147
+ }
148
+
149
+ // Add query params
150
+ fullUrl = buildUrl(fullUrl, params);
151
+
152
+ // Merge headers
153
+ const mergedHeaders = {
154
+ ...globalConfig.headers,
155
+ ...headers
156
+ };
157
+
158
+ // Prepare request init
159
+ const requestInit: RequestInit = {
160
+ ...fetchOptions,
161
+ headers: mergedHeaders,
162
+ credentials: options.credentials ?? globalConfig.credentials
163
+ };
164
+
165
+ // Stringify body if it's an object
166
+ if (body !== undefined) {
167
+ if (body instanceof FormData) {
168
+ requestInit.body = body;
169
+ // Remove Content-Type header for FormData (browser sets it with boundary)
170
+ delete (requestInit.headers as Record<string, string>)['Content-Type'];
171
+ } else if (typeof body === 'object') {
172
+ requestInit.body = JSON.stringify(body);
173
+ } else {
174
+ requestInit.body = body;
175
+ }
176
+ }
177
+
178
+ // Log request
179
+ if (log === true) {
180
+ console.log(`[JUX Fetch] ${requestInit.method || 'GET'} ${fullUrl}`, {
181
+ headers: requestInit.headers,
182
+ body: requestInit.body,
183
+ params
184
+ });
185
+ }
186
+
187
+ // Setup timeout
188
+ const controller = new AbortController();
189
+ const timeoutId = timeout ? setTimeout(() => controller.abort(), timeout) : null;
190
+
191
+ if (!requestInit.signal) {
192
+ requestInit.signal = controller.signal;
193
+ }
194
+
195
+ try {
196
+ const response = await fetch(fullUrl, requestInit);
197
+
198
+ if (timeoutId) clearTimeout(timeoutId);
199
+
200
+ // Handle unauthorized
201
+ if (response.status === 401 || response.status === 403) {
202
+ if (onUnauthorized) {
203
+ onUnauthorized();
204
+ }
205
+ }
206
+
207
+ // Parse response
208
+ let data: T | null = null;
209
+ let errorData: any = null;
210
+
211
+ if (parseResponse) {
212
+ const contentType = response.headers.get('content-type');
213
+
214
+ if (contentType?.includes('application/json')) {
215
+ try {
216
+ const json = await response.json();
217
+ if (response.ok) {
218
+ data = json;
219
+ } else {
220
+ errorData = json;
221
+ }
222
+ } catch (e) {
223
+ // JSON parse error
224
+ }
225
+ } else if (response.ok) {
226
+ // Non-JSON response, try to get text
227
+ try {
228
+ data = (await response.text()) as any;
229
+ } catch (e) {
230
+ // Ignore text parse errors
231
+ }
232
+ }
233
+ }
234
+
235
+ // Check if request was successful
236
+ if (!response.ok) {
237
+ const error: FetchError = {
238
+ message: errorData?.message || errorData?.error || response.statusText || 'Request failed',
239
+ status: response.status,
240
+ statusText: response.statusText,
241
+ data: errorData
242
+ };
243
+
244
+ // Log error
245
+ if (log === true || log === 'errors') {
246
+ console.error(`[JUX Fetch Error] ${requestInit.method || 'GET'} ${fullUrl}`, error);
247
+ }
248
+
249
+ if (onError) {
250
+ onError(error);
251
+ }
252
+
253
+ return {
254
+ data: null,
255
+ error,
256
+ status: response.status,
257
+ response
258
+ };
259
+ }
260
+
261
+ // Log success
262
+ if (log === true) {
263
+ console.log(`[JUX Fetch Success] ${requestInit.method || 'GET'} ${fullUrl}`, {
264
+ status: response.status,
265
+ data
266
+ });
267
+ }
268
+
269
+ return {
270
+ data,
271
+ error: null,
272
+ status: response.status,
273
+ response
274
+ };
275
+
276
+ } catch (err: any) {
277
+ if (timeoutId) clearTimeout(timeoutId);
278
+
279
+ const error: FetchError = {
280
+ message: err.name === 'AbortError'
281
+ ? 'Request timeout'
282
+ : err.message || 'Network error',
283
+ status: 0
284
+ };
285
+
286
+ // Log error
287
+ if (log === true || log === 'errors') {
288
+ console.error(`[JUX Fetch Error] ${requestInit.method || 'GET'} ${fullUrl}`, error);
289
+ }
290
+
291
+ if (onError) {
292
+ onError(error);
293
+ }
294
+
295
+ return {
296
+ data: null,
297
+ error,
298
+ status: 0,
299
+ response: null as any
300
+ };
301
+ }
302
+ }
303
+
304
+ /**
305
+ * FetchBuilder class for method chaining
306
+ */
307
+ class FetchBuilder<T = any> {
308
+ public url: string;
309
+ public options: FetchOptions = {};
310
+
311
+ constructor(url: string, options: FetchOptions = {}) {
312
+ this.url = url;
313
+ this.options = options;
314
+ }
315
+
316
+ method(value: string): this {
317
+ this.options.method = value;
318
+ return this;
319
+ }
320
+
321
+ body(value: any): this {
322
+ this.options.body = value;
323
+ return this;
324
+ }
325
+
326
+ params(value: Record<string, any>): this {
327
+ this.options.params = value;
328
+ return this;
329
+ }
330
+
331
+ headers(value: Record<string, string>): this {
332
+ this.options.headers = { ...this.options.headers, ...value };
333
+ return this;
334
+ }
335
+
336
+ header(key: string, value: string): this {
337
+ this.options.headers = { ...this.options.headers, [key]: value };
338
+ return this;
339
+ }
340
+
341
+ timeout(value: number): this {
342
+ this.options.timeout = value;
343
+ return this;
344
+ }
345
+
346
+ credentials(value: RequestCredentials): this {
347
+ this.options.credentials = value;
348
+ return this;
349
+ }
350
+
351
+ log(value: LogLevel): this {
352
+ this.options.log = value;
353
+ return this;
354
+ }
355
+
356
+ parseResponse(value: boolean): this {
357
+ this.options.parseResponse = value;
358
+ return this;
359
+ }
360
+
361
+ onUnauthorized(callback: () => void): this {
362
+ this.options.onUnauthorized = callback;
363
+ return this;
364
+ }
365
+
366
+ onError(callback: (error: FetchError) => void): this {
367
+ this.options.onError = callback;
368
+ return this;
369
+ }
370
+
371
+ /**
372
+ * Execute the fetch request
373
+ */
374
+ send(): Promise<FetchResult<T>> {
375
+ return executeFetch<T>(this.url, this.options);
376
+ }
377
+
378
+ /**
379
+ * Alias for send()
380
+ */
381
+ fetch(): Promise<FetchResult<T>> {
382
+ return this.send();
383
+ }
384
+
385
+ /**
386
+ * Make the builder thenable (Promise-like) so it can be awaited directly
387
+ * This allows: await jux.fetch('/users') without needing .send()
388
+ */
389
+ then<TResult1 = FetchResult<T>, TResult2 = never>(
390
+ onfulfilled?: ((value: FetchResult<T>) => TResult1 | PromiseLike<TResult1>) | null,
391
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
392
+ ): Promise<TResult1 | TResult2> {
393
+ return this.send().then(onfulfilled, onrejected);
394
+ }
395
+
396
+ /**
397
+ * Make the builder catchable for Promise.catch()
398
+ */
399
+ catch<TResult = never>(
400
+ onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
401
+ ): Promise<FetchResult<T> | TResult> {
402
+ return this.send().catch(onrejected);
403
+ }
404
+
405
+ /**
406
+ * Make the builder finally-able for Promise.finally()
407
+ */
408
+ finally(onfinally?: (() => void) | null): Promise<FetchResult<T>> {
409
+ return this.send().finally(onfinally);
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Create a fetch builder
415
+ */
416
+ export function juxFetch<T = any>(url: string, options: FetchOptions = {}): FetchBuilder<T> {
417
+ return new FetchBuilder<T>(url, options);
418
+ }
419
+
420
+ /**
421
+ * Convenience methods for common HTTP verbs
422
+ */
423
+ export const fetchHelpers = {
424
+ get: <T = any>(url: string, options?: Omit<FetchOptions, 'method'>) =>
425
+ new FetchBuilder<T>(url, { ...options, method: 'GET' }),
426
+
427
+ post: <T = any>(url: string, body?: any, options?: Omit<FetchOptions, 'method' | 'body'>) =>
428
+ new FetchBuilder<T>(url, { ...options, method: 'POST', body }),
429
+
430
+ put: <T = any>(url: string, body?: any, options?: Omit<FetchOptions, 'method' | 'body'>) =>
431
+ new FetchBuilder<T>(url, { ...options, method: 'PUT', body }),
432
+
433
+ patch: <T = any>(url: string, body?: any, options?: Omit<FetchOptions, 'method' | 'body'>) =>
434
+ new FetchBuilder<T>(url, { ...options, method: 'PATCH', body }),
435
+
436
+ delete: <T = any>(url: string, options?: Omit<FetchOptions, 'method'>) =>
437
+ new FetchBuilder<T>(url, { ...options, method: 'DELETE' })
438
+ };
439
+
440
+ /**
441
+ * Fetch multiple URLs in parallel
442
+ */
443
+ export async function fetchAll<T = any>(
444
+ urls: string[],
445
+ options?: FetchOptions
446
+ ): Promise<FetchResult<T>[]> {
447
+ return Promise.all(urls.map(url => executeFetch<T>(url, options)));
448
+ }
449
+
450
+ /**
451
+ * Get a service URL from window.juxConfig
452
+ */
453
+ export function getServiceUrl(serviceName: string): string | null {
454
+ if (typeof window === 'undefined') return null;
455
+
456
+ const juxConfig = (window as any).juxConfig;
457
+ if (!juxConfig?.services) return null;
458
+
459
+ return juxConfig.services[serviceName] || null;
460
+ }
461
+
462
+ /**
463
+ * Create a fetch instance preconfigured for a specific service
464
+ *
465
+ * Usage:
466
+ * const api = jux.fetch.serviceClient('database');
467
+ * const { data } = await api.fetch('/users').send();
468
+ */
469
+ export function serviceClient(serviceName: string) {
470
+ const baseUrl = getServiceUrl(serviceName);
471
+
472
+ if (!baseUrl) {
473
+ console.warn(`[JUX Fetch] Service '${serviceName}' not found in juxConfig.services`);
474
+ }
475
+
476
+ return {
477
+ fetch: (url: string, options?: any) => {
478
+ return juxFetch(url, {
479
+ ...options,
480
+ baseUrl: baseUrl || undefined
481
+ });
482
+ },
483
+ baseUrl
484
+ };
485
+ }
486
+
487
+ /**
488
+ * Setup fetch from juxconfig
489
+ * Call this in your bootstrap function
490
+ *
491
+ * Usage:
492
+ * export default {
493
+ * bootstrap: [
494
+ * async function initFetch() {
495
+ * await jux.fetch.setupConfig();
496
+ * }
497
+ * ]
498
+ * };
499
+ */
500
+ export async function setupConfig() {
501
+ if (typeof window === 'undefined') {
502
+ console.warn('[JUX Fetch] setupConfig() called outside browser environment');
503
+ return;
504
+ }
505
+
506
+ const juxConfig = (window as any).juxConfig;
507
+
508
+ if (!juxConfig) {
509
+ console.warn('[JUX Fetch] No juxConfig found on window. Make sure your juxconfig.js is loaded.');
510
+ return;
511
+ }
512
+
513
+ const services = juxConfig.services || {};
514
+
515
+ if (Object.keys(services).length === 0) {
516
+ console.log('[JUX Fetch] No services configured in juxConfig');
517
+ return;
518
+ }
519
+
520
+ // Configure fetch with common defaults
521
+ const fetchConfig: FetchConfig = {
522
+ timeout: juxConfig.fetchTimeout || 30000,
523
+ log: juxConfig.fetchLog || false,
524
+ credentials: 'include'
525
+ };
526
+
527
+ // Add service-specific handlers
528
+ if (services.auth) {
529
+ fetchConfig.onUnauthorized = () => {
530
+ console.warn('[JUX Fetch] Unauthorized - consider redirecting to login');
531
+ };
532
+ }
533
+
534
+ configureFetch(fetchConfig);
535
+
536
+ // Log configured services
537
+ console.log('[JUX Fetch] Configured with services:', Object.keys(services));
538
+
539
+ return {
540
+ services,
541
+ getServiceUrl
542
+ };
543
+ }
544
+
545
+ // Export combined API
546
+ export const fetchAPI = Object.assign(juxFetch, {
547
+ config: configureFetch,
548
+ setupConfig,
549
+ getServiceUrl,
550
+ serviceClient,
551
+ ...fetchHelpers,
552
+ all: fetchAll
553
+ });
@@ -91,6 +91,84 @@ export class JuxCompiler {
91
91
  return name.replace(/[^a-zA-Z0-9]/g, '_');
92
92
  }
93
93
 
94
+ async loadJuxscriptExports() {
95
+ if (this._juxscriptExports) return this._juxscriptExports;
96
+
97
+ try {
98
+ const juxscriptPath = this.findJuxscriptPath();
99
+ if (juxscriptPath) {
100
+ const indexContent = fs.readFileSync(juxscriptPath, 'utf8');
101
+ const exports = new Set();
102
+
103
+ for (const match of indexContent.matchAll(/export\s*\{\s*([^}]+)\s*\}/g)) {
104
+ match[1].split(',').forEach(exp => {
105
+ const name = exp.trim().split(/\s+as\s+/)[0].trim();
106
+ if (name) exports.add(name);
107
+ });
108
+ }
109
+
110
+ this._juxscriptExports = [...exports];
111
+ if (this._juxscriptExports.length > 0) {
112
+ console.log(`📦 juxscript exports: ${this._juxscriptExports.join(', ')}`);
113
+ }
114
+ }
115
+ } catch (err) {
116
+ this._juxscriptExports = [];
117
+ }
118
+
119
+ return this._juxscriptExports;
120
+ }
121
+
122
+ validateViewCode(viewName, code) {
123
+ const issues = [];
124
+
125
+ let ast;
126
+ try {
127
+ ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
128
+ } catch (parseError) {
129
+ issues.push({
130
+ type: 'error',
131
+ view: viewName,
132
+ line: parseError.loc?.line || 0,
133
+ message: `Syntax error: ${parseError.message}`,
134
+ code: ''
135
+ });
136
+ return issues;
137
+ }
138
+
139
+ const allImports = new Set();
140
+
141
+ walk(ast, {
142
+ ImportDeclaration(node) {
143
+ node.specifiers.forEach(spec => {
144
+ allImports.add(spec.local.name);
145
+ });
146
+ }
147
+ });
148
+
149
+ // Default known facades/components if load fails
150
+ const knownComponents = this._juxscriptExports || ['element', 'input', 'buttonGroup'];
151
+
152
+ walk(ast, {
153
+ Identifier(node, parent) {
154
+ if (parent?.type === 'CallExpression' && parent.callee === node) {
155
+ const name = node.name;
156
+ if (!allImports.has(name) && knownComponents.includes(name)) {
157
+ issues.push({
158
+ type: 'warning',
159
+ view: viewName,
160
+ line: node.loc?.start?.line || 0,
161
+ message: `"${name}" is used but not imported from juxscript`,
162
+ code: ''
163
+ });
164
+ }
165
+ }
166
+ }
167
+ });
168
+
169
+ return issues;
170
+ }
171
+
94
172
  /**
95
173
  * Generate entry point without layout/theme logic
96
174
  */