@alepha/react 0.13.6 → 0.13.8

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 (37) hide show
  1. package/dist/auth/index.browser.js +5 -5
  2. package/dist/auth/index.browser.js.map +1 -1
  3. package/dist/auth/index.d.ts +105 -103
  4. package/dist/auth/index.js +5 -5
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/core/index.browser.js +407 -142
  7. package/dist/core/index.browser.js.map +1 -1
  8. package/dist/core/index.d.ts +144 -116
  9. package/dist/core/index.js +409 -145
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/core/index.native.js +24 -2
  12. package/dist/core/index.native.js.map +1 -1
  13. package/dist/form/index.d.ts +14 -6
  14. package/dist/form/index.js +32 -12
  15. package/dist/form/index.js.map +1 -1
  16. package/dist/head/index.d.ts +18 -18
  17. package/dist/head/index.js +5 -1
  18. package/dist/head/index.js.map +1 -1
  19. package/dist/i18n/index.d.ts +25 -25
  20. package/dist/i18n/index.js +4 -3
  21. package/dist/i18n/index.js.map +1 -1
  22. package/dist/websocket/index.d.ts +1 -1
  23. package/package.json +22 -23
  24. package/src/auth/hooks/useAuth.ts +1 -0
  25. package/src/auth/services/ReactAuth.ts +6 -4
  26. package/src/core/components/ErrorViewer.tsx +378 -130
  27. package/src/core/components/NestedView.tsx +16 -11
  28. package/src/core/contexts/AlephaProvider.tsx +41 -0
  29. package/src/core/contexts/RouterLayerContext.ts +2 -0
  30. package/src/core/hooks/useAction.ts +4 -1
  31. package/src/core/index.shared.ts +1 -0
  32. package/src/core/primitives/$page.ts +15 -2
  33. package/src/core/providers/ReactPageProvider.ts +6 -7
  34. package/src/core/providers/ReactServerProvider.ts +2 -6
  35. package/src/form/services/FormModel.ts +81 -26
  36. package/src/head/index.ts +2 -1
  37. package/src/i18n/providers/I18nProvider.ts +4 -2
@@ -1,163 +1,411 @@
1
1
  import type { Alepha } from "alepha";
2
- import { useState } from "react";
2
+ import { type CSSProperties, useState } from "react";
3
3
 
4
4
  interface ErrorViewerProps {
5
5
  error: Error;
6
6
  alepha: Alepha;
7
7
  }
8
8
 
9
- // TODO: design this better
9
+ interface StackFrame {
10
+ fn: string;
11
+ file: string;
12
+ line: string;
13
+ col: string;
14
+ raw: string;
15
+ }
10
16
 
17
+ /**
18
+ * Error viewer component that displays error details in development mode
19
+ */
11
20
  const ErrorViewer = ({ error, alepha }: ErrorViewerProps) => {
12
21
  const [expanded, setExpanded] = useState(false);
13
22
  const isProduction = alepha.isProduction();
14
- // const status = isHttpError(error) ? error.status : 500;
15
23
 
16
24
  if (isProduction) {
17
25
  return <ErrorViewerProduction />;
18
26
  }
19
27
 
20
- const stackLines = error.stack?.split("\n") ?? [];
21
- const previewLines = stackLines.slice(0, 5);
22
- const hiddenLineCount = stackLines.length - previewLines.length;
28
+ const frames = parseStackTrace(error.stack);
29
+ const visibleFrames = expanded ? frames : frames.slice(0, 6);
30
+ const hiddenCount = frames.length - 6;
23
31
 
24
- const copyToClipboard = (text: string) => {
25
- navigator.clipboard.writeText(text).catch((err) => {
26
- console.error("Clipboard error:", err);
27
- });
28
- };
32
+ return (
33
+ <div style={styles.overlay}>
34
+ <div style={styles.container}>
35
+ <Header error={error} />
36
+ <StackTraceSection
37
+ frames={frames}
38
+ visibleFrames={visibleFrames}
39
+ expanded={expanded}
40
+ hiddenCount={hiddenCount}
41
+ onToggle={() => setExpanded(!expanded)}
42
+ />
43
+ </div>
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export default ErrorViewer;
49
+
50
+ /**
51
+ * Parse stack trace string into structured frames
52
+ */
53
+ function parseStackTrace(stack?: string): StackFrame[] {
54
+ if (!stack) return [];
55
+
56
+ const lines = stack.split("\n").slice(1);
57
+ const frames: StackFrame[] = [];
58
+
59
+ for (const line of lines) {
60
+ const trimmed = line.trim();
61
+ if (!trimmed.startsWith("at ")) continue;
62
+
63
+ const frame = parseStackLine(trimmed);
64
+ if (frame) frames.push(frame);
65
+ }
66
+
67
+ return frames;
68
+ }
69
+
70
+ /**
71
+ * Parse a single stack trace line into a structured frame
72
+ */
73
+ function parseStackLine(line: string): StackFrame | null {
74
+ const withFn = line.match(/^at\s+(.+?)\s+\((.+):(\d+):(\d+)\)$/);
75
+ if (withFn) {
76
+ return {
77
+ fn: withFn[1],
78
+ file: withFn[2],
79
+ line: withFn[3],
80
+ col: withFn[4],
81
+ raw: line,
82
+ };
83
+ }
84
+
85
+ const withoutFn = line.match(/^at\s+(.+):(\d+):(\d+)$/);
86
+ if (withoutFn) {
87
+ return {
88
+ fn: "<anonymous>",
89
+ file: withoutFn[1],
90
+ line: withoutFn[2],
91
+ col: withoutFn[3],
92
+ raw: line,
93
+ };
94
+ }
95
+
96
+ return { fn: "", file: line.replace(/^at\s+/, ""), line: "", col: "", raw: line };
97
+ }
98
+
99
+ /**
100
+ * Copy text to clipboard
101
+ */
102
+ function copyToClipboard(text: string): void {
103
+ navigator.clipboard.writeText(text).catch((err) => {
104
+ console.error("Clipboard error:", err);
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Header section with error type and message
110
+ */
111
+ function Header({ error }: { error: Error }) {
112
+ const [copied, setCopied] = useState(false);
29
113
 
30
- const styles = {
31
- container: {
32
- padding: "24px",
33
- backgroundColor: "#FEF2F2",
34
- color: "#7F1D1D",
35
- border: "1px solid #FECACA",
36
- borderRadius: "16px",
37
- boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
38
- fontFamily: "monospace",
39
- maxWidth: "768px",
40
- margin: "40px auto",
41
- },
42
- heading: {
43
- fontSize: "20px",
44
- fontWeight: "bold",
45
- marginBottom: "10px",
46
- },
47
- name: {
48
- fontSize: "16px",
49
- fontWeight: 600,
50
- },
51
- message: {
52
- fontSize: "14px",
53
- marginBottom: "16px",
54
- },
55
- sectionHeader: {
56
- display: "flex",
57
- justifyContent: "space-between",
58
- alignItems: "center",
59
- fontSize: "12px",
60
- marginBottom: "4px",
61
- color: "#991B1B",
62
- },
63
- copyButton: {
64
- fontSize: "12px",
65
- color: "#DC2626",
66
- background: "none",
67
- border: "none",
68
- cursor: "pointer",
69
- textDecoration: "underline",
70
- },
71
- stackContainer: {
72
- backgroundColor: "#FEE2E2",
73
- padding: "12px",
74
- borderRadius: "8px",
75
- fontSize: "13px",
76
- lineHeight: "1.4",
77
- overflowX: "auto" as const,
78
- whiteSpace: "pre-wrap" as const,
79
- },
80
- expandLine: {
81
- color: "#F87171",
82
- cursor: "pointer",
83
- marginTop: "8px",
84
- },
114
+ const handleCopy = () => {
115
+ copyToClipboard(error.stack || error.message);
116
+ setCopied(true);
117
+ setTimeout(() => setCopied(false), 2000);
85
118
  };
86
119
 
87
120
  return (
88
- <div style={styles.container}>
89
- <div>
90
- <div style={styles.heading}>🔥 Error</div>
91
- <div style={styles.name}>{error.name}</div>
92
- <div style={styles.message}>{error.message}</div>
121
+ <div style={styles.header}>
122
+ <div style={styles.headerTop}>
123
+ <div style={styles.badge}>{error.name}</div>
124
+ <button type="button" onClick={handleCopy} style={styles.copyBtn}>
125
+ {copied ? "Copied" : "Copy Stack"}
126
+ </button>
93
127
  </div>
128
+ <h1 style={styles.message}>{error.message}</h1>
129
+ </div>
130
+ );
131
+ }
94
132
 
95
- {stackLines.length > 0 && (
96
- <div>
97
- <div style={styles.sectionHeader}>
98
- <span>Stack trace</span>
99
- <button
100
- type="button"
101
- onClick={() => copyToClipboard(error.stack!)}
102
- style={styles.copyButton}
103
- >
104
- Copy all
105
- </button>
106
- </div>
107
- <pre style={styles.stackContainer}>
108
- {(expanded ? stackLines : previewLines).map((line, i) => (
109
- <div key={i}>{line}</div>
110
- ))}
111
- {!expanded && hiddenLineCount > 0 && (
112
- <div style={styles.expandLine} onClick={() => setExpanded(true)}>
113
- + {hiddenLineCount} more lines...
114
- </div>
115
- )}
116
- </pre>
117
- </div>
118
- )}
133
+ /**
134
+ * Stack trace section with expandable frames
135
+ */
136
+ function StackTraceSection({
137
+ frames,
138
+ visibleFrames,
139
+ expanded,
140
+ hiddenCount,
141
+ onToggle,
142
+ }: {
143
+ frames: StackFrame[];
144
+ visibleFrames: StackFrame[];
145
+ expanded: boolean;
146
+ hiddenCount: number;
147
+ onToggle: () => void;
148
+ }) {
149
+ if (frames.length === 0) return null;
150
+
151
+ return (
152
+ <div style={styles.stackSection}>
153
+ <div style={styles.stackHeader}>Call Stack</div>
154
+ <div style={styles.frameList}>
155
+ {visibleFrames.map((frame, i) => (
156
+ <StackFrameRow key={i} frame={frame} index={i} />
157
+ ))}
158
+ {!expanded && hiddenCount > 0 && (
159
+ <button type="button" onClick={onToggle} style={styles.expandBtn}>
160
+ Show {hiddenCount} more frames
161
+ </button>
162
+ )}
163
+ {expanded && hiddenCount > 0 && (
164
+ <button type="button" onClick={onToggle} style={styles.expandBtn}>
165
+ Show less
166
+ </button>
167
+ )}
168
+ </div>
119
169
  </div>
120
170
  );
121
- };
171
+ }
122
172
 
123
- export default ErrorViewer;
173
+ /**
174
+ * Single stack frame row
175
+ */
176
+ function StackFrameRow({ frame, index }: { frame: StackFrame; index: number }) {
177
+ const isFirst = index === 0;
178
+ const fileName = frame.file.split("/").pop() || frame.file;
179
+ const dirPath = frame.file.substring(0, frame.file.length - fileName.length);
124
180
 
125
- const ErrorViewerProduction = () => {
126
- const styles = {
127
- container: {
128
- padding: "24px",
129
- backgroundColor: "#FEF2F2",
130
- color: "#7F1D1D",
131
- border: "1px solid #FECACA",
132
- borderRadius: "16px",
133
- boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
134
- fontFamily: "monospace",
135
- maxWidth: "768px",
136
- margin: "40px auto",
137
- textAlign: "center" as const,
138
- },
139
- heading: {
140
- fontSize: "20px",
141
- fontWeight: "bold",
142
- marginBottom: "8px",
143
- },
144
- name: {
145
- fontSize: "16px",
146
- fontWeight: 600,
147
- marginBottom: "4px",
148
- },
149
- message: {
150
- fontSize: "14px",
151
- opacity: 0.85,
152
- },
153
- };
181
+ return (
182
+ <div
183
+ style={{
184
+ ...styles.frame,
185
+ ...(isFirst ? styles.frameFirst : {}),
186
+ }}
187
+ >
188
+ <div style={styles.frameIndex}>{index + 1}</div>
189
+ <div style={styles.frameContent}>
190
+ {frame.fn && (
191
+ <div style={styles.fnName}>
192
+ {frame.fn}
193
+ </div>
194
+ )}
195
+ <div style={styles.filePath}>
196
+ <span style={styles.dirPath}>{dirPath}</span>
197
+ <span style={styles.fileName}>{fileName}</span>
198
+ {frame.line && (
199
+ <span style={styles.lineCol}>
200
+ :{frame.line}:{frame.col}
201
+ </span>
202
+ )}
203
+ </div>
204
+ </div>
205
+ </div>
206
+ );
207
+ }
154
208
 
209
+ /**
210
+ * Production error view - minimal information
211
+ */
212
+ function ErrorViewerProduction() {
155
213
  return (
156
- <div style={styles.container}>
157
- <div style={styles.heading}>🚨 An error occurred</div>
158
- <div style={styles.message}>
159
- Something went wrong. Please try again later.
214
+ <div style={styles.overlay}>
215
+ <div style={styles.prodContainer}>
216
+ <div style={styles.prodIcon}>!</div>
217
+ <h1 style={styles.prodTitle}>Application Error</h1>
218
+ <p style={styles.prodMessage}>
219
+ An unexpected error occurred. Please try again later.
220
+ </p>
221
+ <button
222
+ type="button"
223
+ onClick={() => window.location.reload()}
224
+ style={styles.prodButton}
225
+ >
226
+ Reload Page
227
+ </button>
160
228
  </div>
161
229
  </div>
162
230
  );
231
+ }
232
+
233
+ const styles: Record<string, CSSProperties> = {
234
+ overlay: {
235
+ position: "fixed",
236
+ inset: 0,
237
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
238
+ display: "flex",
239
+ alignItems: "flex-start",
240
+ justifyContent: "center",
241
+ padding: "40px 20px",
242
+ overflow: "auto",
243
+ fontFamily:
244
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
245
+ zIndex: 99999,
246
+ },
247
+ container: {
248
+ width: "100%",
249
+ maxWidth: "960px",
250
+ backgroundColor: "#1a1a1a",
251
+ borderRadius: "12px",
252
+ overflow: "hidden",
253
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
254
+ },
255
+ header: {
256
+ padding: "24px 28px",
257
+ borderBottom: "1px solid #333",
258
+ background: "linear-gradient(to bottom, #1f1f1f, #1a1a1a)",
259
+ },
260
+ headerTop: {
261
+ display: "flex",
262
+ alignItems: "center",
263
+ justifyContent: "space-between",
264
+ marginBottom: "16px",
265
+ },
266
+ badge: {
267
+ display: "inline-block",
268
+ padding: "6px 12px",
269
+ backgroundColor: "#dc2626",
270
+ color: "#fff",
271
+ fontSize: "12px",
272
+ fontWeight: 600,
273
+ borderRadius: "6px",
274
+ letterSpacing: "0.025em",
275
+ },
276
+ copyBtn: {
277
+ padding: "8px 16px",
278
+ backgroundColor: "transparent",
279
+ color: "#888",
280
+ fontSize: "13px",
281
+ fontWeight: 500,
282
+ border: "1px solid #444",
283
+ borderRadius: "6px",
284
+ cursor: "pointer",
285
+ transition: "all 0.15s",
286
+ },
287
+ message: {
288
+ margin: 0,
289
+ fontSize: "20px",
290
+ fontWeight: 500,
291
+ color: "#fff",
292
+ lineHeight: 1.5,
293
+ wordBreak: "break-word",
294
+ },
295
+ stackSection: {
296
+ padding: "0",
297
+ },
298
+ stackHeader: {
299
+ padding: "16px 28px",
300
+ fontSize: "11px",
301
+ fontWeight: 600,
302
+ color: "#666",
303
+ textTransform: "uppercase",
304
+ letterSpacing: "0.1em",
305
+ borderBottom: "1px solid #2a2a2a",
306
+ },
307
+ frameList: {
308
+ display: "flex",
309
+ flexDirection: "column",
310
+ },
311
+ frame: {
312
+ display: "flex",
313
+ alignItems: "flex-start",
314
+ padding: "14px 28px",
315
+ borderBottom: "1px solid #252525",
316
+ transition: "background-color 0.15s",
317
+ },
318
+ frameFirst: {
319
+ backgroundColor: "rgba(220, 38, 38, 0.1)",
320
+ },
321
+ frameIndex: {
322
+ width: "28px",
323
+ flexShrink: 0,
324
+ fontSize: "12px",
325
+ fontWeight: 500,
326
+ color: "#555",
327
+ fontFamily: "monospace",
328
+ },
329
+ frameContent: {
330
+ flex: 1,
331
+ minWidth: 0,
332
+ },
333
+ fnName: {
334
+ fontSize: "14px",
335
+ fontWeight: 500,
336
+ color: "#e5e5e5",
337
+ marginBottom: "4px",
338
+ fontFamily:
339
+ 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
340
+ },
341
+ filePath: {
342
+ fontSize: "13px",
343
+ color: "#888",
344
+ fontFamily:
345
+ 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
346
+ wordBreak: "break-all",
347
+ },
348
+ dirPath: {
349
+ color: "#555",
350
+ },
351
+ fileName: {
352
+ color: "#0ea5e9",
353
+ },
354
+ lineCol: {
355
+ color: "#eab308",
356
+ },
357
+ expandBtn: {
358
+ padding: "16px 28px",
359
+ backgroundColor: "transparent",
360
+ color: "#666",
361
+ fontSize: "13px",
362
+ fontWeight: 500,
363
+ border: "none",
364
+ borderTop: "1px solid #252525",
365
+ cursor: "pointer",
366
+ textAlign: "left",
367
+ transition: "all 0.15s",
368
+ },
369
+ prodContainer: {
370
+ textAlign: "center",
371
+ padding: "60px 40px",
372
+ backgroundColor: "#1a1a1a",
373
+ borderRadius: "12px",
374
+ maxWidth: "400px",
375
+ },
376
+ prodIcon: {
377
+ width: "64px",
378
+ height: "64px",
379
+ margin: "0 auto 24px",
380
+ backgroundColor: "#dc2626",
381
+ borderRadius: "50%",
382
+ display: "flex",
383
+ alignItems: "center",
384
+ justifyContent: "center",
385
+ fontSize: "32px",
386
+ fontWeight: 700,
387
+ color: "#fff",
388
+ },
389
+ prodTitle: {
390
+ margin: "0 0 12px",
391
+ fontSize: "24px",
392
+ fontWeight: 600,
393
+ color: "#fff",
394
+ },
395
+ prodMessage: {
396
+ margin: "0 0 28px",
397
+ fontSize: "15px",
398
+ color: "#888",
399
+ lineHeight: 1.6,
400
+ },
401
+ prodButton: {
402
+ padding: "12px 24px",
403
+ backgroundColor: "#fff",
404
+ color: "#000",
405
+ fontSize: "14px",
406
+ fontWeight: 600,
407
+ border: "none",
408
+ borderRadius: "8px",
409
+ cursor: "pointer",
410
+ },
163
411
  };
@@ -1,11 +1,13 @@
1
1
  import { memo, type ReactNode, use, useRef, useState } from "react";
2
2
  import { RouterLayerContext } from "../contexts/RouterLayerContext.ts";
3
- import type { PageAnimation } from "../primitives/$page.ts";
3
+ import type { ErrorHandler, PageAnimation } from "../primitives/$page.ts";
4
4
  import { Redirection } from "../errors/Redirection.ts";
5
5
  import { useEvents } from "../hooks/useEvents.ts";
6
6
  import { useRouterState } from "../hooks/useRouterState.ts";
7
7
  import type { ReactRouterState } from "../providers/ReactPageProvider.ts";
8
8
  import ErrorBoundary from "./ErrorBoundary.tsx";
9
+ import ErrorViewer from "./ErrorViewer.tsx";
10
+ import { useAlepha } from "../hooks/useAlepha.ts";
9
11
 
10
12
  export interface NestedViewProps {
11
13
  children?: ReactNode;
@@ -34,8 +36,11 @@ export interface NestedViewProps {
34
36
  * ```
35
37
  */
36
38
  const NestedView = (props: NestedViewProps) => {
37
- const index = use(RouterLayerContext)?.index ?? 0;
39
+ const routerLayer = use(RouterLayerContext);
40
+ const index = routerLayer?.index ?? 0;
41
+ const onError = routerLayer?.onError;
38
42
  const state = useRouterState();
43
+ const alepha = useAlepha();
39
44
 
40
45
  const [view, setView] = useState<ReactNode | undefined>(
41
46
  state.layers[index]?.element,
@@ -148,16 +153,16 @@ const NestedView = (props: NestedViewProps) => {
148
153
  );
149
154
  }
150
155
 
156
+ const fallback = (error: Error) => {
157
+ const result = onError?.(error, state) ?? <ErrorViewer error={error} alepha={alepha}/>;
158
+ if (result instanceof Redirection) {
159
+ return "Redirection inside ErrorBoundary is not allowed.";
160
+ }
161
+ return result as ReactNode;
162
+ }
163
+
151
164
  return (
152
- <ErrorBoundary
153
- fallback={(error) => {
154
- const result = state.onError(error, state); // TODO: onError is not refreshed
155
- if (result instanceof Redirection) {
156
- return "Redirection inside ErrorBoundary is not allowed.";
157
- }
158
- return result as ReactNode;
159
- }}
160
- >
165
+ <ErrorBoundary fallback={fallback}>
161
166
  {element}
162
167
  </ErrorBoundary>
163
168
  );
@@ -0,0 +1,41 @@
1
+ import { Alepha } from "alepha";
2
+ import { type ReactNode, useEffect, useMemo, useState } from "react";
3
+ import { AlephaContext } from "./AlephaContext.ts";
4
+
5
+ export interface AlephaProviderProps {
6
+ children: ReactNode;
7
+ onError: (error: Error) => ReactNode;
8
+ onLoading: () => ReactNode;
9
+ }
10
+
11
+ /**
12
+ * AlephaProvider component to initialize and provide Alepha instance to the app.
13
+ * This isn't recommended for apps using alepha/react/router, as Router will handle this for you.
14
+ */
15
+ export const AlephaProvider = (props: AlephaProviderProps) => {
16
+ const alepha = useMemo(() => Alepha.create(), []);
17
+
18
+ const [started, setStarted] = useState(false);
19
+ const [error, setError] = useState<Error | undefined>();
20
+
21
+ useEffect(() => {
22
+ alepha
23
+ .start()
24
+ .then(() => setStarted(true))
25
+ .catch((err) => setError(err));
26
+ }, [alepha]);
27
+
28
+ if (error) {
29
+ return props.onError(error);
30
+ }
31
+
32
+ if (!started) {
33
+ return props.onLoading();
34
+ }
35
+
36
+ return (
37
+ <AlephaContext.Provider value={alepha}>
38
+ {props.children}
39
+ </AlephaContext.Provider>
40
+ );
41
+ };
@@ -1,8 +1,10 @@
1
1
  import { createContext } from "react";
2
+ import type { ErrorHandler } from "../primitives/$page.ts";
2
3
 
3
4
  export interface RouterLayerContextValue {
4
5
  index: number;
5
6
  path: string;
7
+ onError: ErrorHandler;
6
8
  }
7
9
 
8
10
  export const RouterLayerContext = createContext<
@@ -13,6 +13,7 @@ import {
13
13
  } from "react";
14
14
  import { useAlepha } from "./useAlepha.ts";
15
15
  import { useInject } from "./useInject.ts";
16
+ import type { Async } from "alepha";
16
17
 
17
18
  /**
18
19
  * Hook for handling async actions with automatic error handling and event emission.
@@ -361,7 +362,7 @@ export interface UseActionOptions<Args extends any[] = any[], Result = any> {
361
362
  * The async action handler function.
362
363
  * Receives the action arguments plus an ActionContext as the last parameter.
363
364
  */
364
- handler: (...args: [...Args, ActionContext]) => Promise<Result>;
365
+ handler: (...args: [...Args, ActionContext]) => Async<Result>;
365
366
 
366
367
  /**
367
368
  * Custom error handler. If provided, prevents default error re-throw.
@@ -378,6 +379,8 @@ export interface UseActionOptions<Args extends any[] = any[], Result = any> {
378
379
  */
379
380
  id?: string;
380
381
 
382
+ name?: string;
383
+
381
384
  /**
382
385
  * Debounce delay in milliseconds. If specified, the action will only execute
383
386
  * after the specified delay has passed since the last call. Useful for search inputs
@@ -1,3 +1,4 @@
1
+ export * from "./contexts/AlephaProvider.tsx";
1
2
  export * from "./contexts/AlephaContext.ts";
2
3
  export * from "./hooks/useAction.ts";
3
4
  export * from "./hooks/useAlepha.ts";