@alepha/react 0.13.6 → 0.13.7

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.
@@ -169,150 +169,388 @@ var ClientOnly_default = ClientOnly;
169
169
 
170
170
  //#endregion
171
171
  //#region ../../src/core/components/ErrorViewer.tsx
172
+ /**
173
+ * Error viewer component that displays error details in development mode
174
+ */
172
175
  const ErrorViewer = ({ error, alepha }) => {
173
176
  const [expanded, setExpanded] = useState(false);
174
177
  if (alepha.isProduction()) return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
175
- const stackLines = error.stack?.split("\n") ?? [];
176
- const previewLines = stackLines.slice(0, 5);
177
- const hiddenLineCount = stackLines.length - previewLines.length;
178
- const copyToClipboard = (text) => {
179
- navigator.clipboard.writeText(text).catch((err) => {
180
- console.error("Clipboard error:", err);
181
- });
178
+ const frames = parseStackTrace(error.stack);
179
+ const visibleFrames = expanded ? frames : frames.slice(0, 6);
180
+ const hiddenCount = frames.length - 6;
181
+ return /* @__PURE__ */ jsx("div", {
182
+ style: styles.overlay,
183
+ children: /* @__PURE__ */ jsxs("div", {
184
+ style: styles.container,
185
+ children: [/* @__PURE__ */ jsx(Header, { error }), /* @__PURE__ */ jsx(StackTraceSection, {
186
+ frames,
187
+ visibleFrames,
188
+ expanded,
189
+ hiddenCount,
190
+ onToggle: () => setExpanded(!expanded)
191
+ })]
192
+ })
193
+ });
194
+ };
195
+ var ErrorViewer_default = ErrorViewer;
196
+ /**
197
+ * Parse stack trace string into structured frames
198
+ */
199
+ function parseStackTrace(stack) {
200
+ if (!stack) return [];
201
+ const lines = stack.split("\n").slice(1);
202
+ const frames = [];
203
+ for (const line of lines) {
204
+ const trimmed = line.trim();
205
+ if (!trimmed.startsWith("at ")) continue;
206
+ const frame = parseStackLine(trimmed);
207
+ if (frame) frames.push(frame);
208
+ }
209
+ return frames;
210
+ }
211
+ /**
212
+ * Parse a single stack trace line into a structured frame
213
+ */
214
+ function parseStackLine(line) {
215
+ const withFn = line.match(/^at\s+(.+?)\s+\((.+):(\d+):(\d+)\)$/);
216
+ if (withFn) return {
217
+ fn: withFn[1],
218
+ file: withFn[2],
219
+ line: withFn[3],
220
+ col: withFn[4],
221
+ raw: line
182
222
  };
183
- const styles = {
184
- container: {
185
- padding: "24px",
186
- backgroundColor: "#FEF2F2",
187
- color: "#7F1D1D",
188
- border: "1px solid #FECACA",
189
- borderRadius: "16px",
190
- boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
191
- fontFamily: "monospace",
192
- maxWidth: "768px",
193
- margin: "40px auto"
194
- },
195
- heading: {
196
- fontSize: "20px",
197
- fontWeight: "bold",
198
- marginBottom: "10px"
199
- },
200
- name: {
201
- fontSize: "16px",
202
- fontWeight: 600
203
- },
204
- message: {
205
- fontSize: "14px",
206
- marginBottom: "16px"
207
- },
208
- sectionHeader: {
209
- display: "flex",
210
- justifyContent: "space-between",
211
- alignItems: "center",
212
- fontSize: "12px",
213
- marginBottom: "4px",
214
- color: "#991B1B"
215
- },
216
- copyButton: {
217
- fontSize: "12px",
218
- color: "#DC2626",
219
- background: "none",
220
- border: "none",
221
- cursor: "pointer",
222
- textDecoration: "underline"
223
- },
224
- stackContainer: {
225
- backgroundColor: "#FEE2E2",
226
- padding: "12px",
227
- borderRadius: "8px",
228
- fontSize: "13px",
229
- lineHeight: "1.4",
230
- overflowX: "auto",
231
- whiteSpace: "pre-wrap"
232
- },
233
- expandLine: {
234
- color: "#F87171",
235
- cursor: "pointer",
236
- marginTop: "8px"
237
- }
223
+ const withoutFn = line.match(/^at\s+(.+):(\d+):(\d+)$/);
224
+ if (withoutFn) return {
225
+ fn: "<anonymous>",
226
+ file: withoutFn[1],
227
+ line: withoutFn[2],
228
+ col: withoutFn[3],
229
+ raw: line
230
+ };
231
+ return {
232
+ fn: "",
233
+ file: line.replace(/^at\s+/, ""),
234
+ line: "",
235
+ col: "",
236
+ raw: line
237
+ };
238
+ }
239
+ /**
240
+ * Copy text to clipboard
241
+ */
242
+ function copyToClipboard(text) {
243
+ navigator.clipboard.writeText(text).catch((err) => {
244
+ console.error("Clipboard error:", err);
245
+ });
246
+ }
247
+ /**
248
+ * Header section with error type and message
249
+ */
250
+ function Header({ error }) {
251
+ const [copied, setCopied] = useState(false);
252
+ const handleCopy = () => {
253
+ copyToClipboard(error.stack || error.message);
254
+ setCopied(true);
255
+ setTimeout(() => setCopied(false), 2e3);
238
256
  };
239
257
  return /* @__PURE__ */ jsxs("div", {
240
- style: styles.container,
241
- children: [/* @__PURE__ */ jsxs("div", { children: [
242
- /* @__PURE__ */ jsx("div", {
243
- style: styles.heading,
244
- children: "🔥 Error"
245
- }),
246
- /* @__PURE__ */ jsx("div", {
247
- style: styles.name,
258
+ style: styles.header,
259
+ children: [/* @__PURE__ */ jsxs("div", {
260
+ style: styles.headerTop,
261
+ children: [/* @__PURE__ */ jsx("div", {
262
+ style: styles.badge,
248
263
  children: error.name
249
- }),
250
- /* @__PURE__ */ jsx("div", {
251
- style: styles.message,
252
- children: error.message
253
- })
254
- ] }), stackLines.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
255
- style: styles.sectionHeader,
256
- children: [/* @__PURE__ */ jsx("span", { children: "Stack trace" }), /* @__PURE__ */ jsx("button", {
264
+ }), /* @__PURE__ */ jsx("button", {
257
265
  type: "button",
258
- onClick: () => copyToClipboard(error.stack),
259
- style: styles.copyButton,
260
- children: "Copy all"
266
+ onClick: handleCopy,
267
+ style: styles.copyBtn,
268
+ children: copied ? "Copied" : "Copy Stack"
261
269
  })]
262
- }), /* @__PURE__ */ jsxs("pre", {
263
- style: styles.stackContainer,
264
- children: [(expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ jsx("div", { children: line }, i)), !expanded && hiddenLineCount > 0 && /* @__PURE__ */ jsxs("div", {
265
- style: styles.expandLine,
266
- onClick: () => setExpanded(true),
267
- children: [
268
- "+ ",
269
- hiddenLineCount,
270
- " more lines..."
271
- ]
272
- })]
273
- })] })]
270
+ }), /* @__PURE__ */ jsx("h1", {
271
+ style: styles.message,
272
+ children: error.message
273
+ })]
274
274
  });
275
- };
276
- var ErrorViewer_default = ErrorViewer;
277
- const ErrorViewerProduction = () => {
278
- const styles = {
279
- container: {
280
- padding: "24px",
281
- backgroundColor: "#FEF2F2",
282
- color: "#7F1D1D",
283
- border: "1px solid #FECACA",
284
- borderRadius: "16px",
285
- boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
286
- fontFamily: "monospace",
287
- maxWidth: "768px",
288
- margin: "40px auto",
289
- textAlign: "center"
290
- },
291
- heading: {
292
- fontSize: "20px",
293
- fontWeight: "bold",
294
- marginBottom: "8px"
295
- },
296
- name: {
297
- fontSize: "16px",
298
- fontWeight: 600,
299
- marginBottom: "4px"
300
- },
301
- message: {
302
- fontSize: "14px",
303
- opacity: .85
304
- }
305
- };
275
+ }
276
+ /**
277
+ * Stack trace section with expandable frames
278
+ */
279
+ function StackTraceSection({ frames, visibleFrames, expanded, hiddenCount, onToggle }) {
280
+ if (frames.length === 0) return null;
306
281
  return /* @__PURE__ */ jsxs("div", {
307
- style: styles.container,
282
+ style: styles.stackSection,
308
283
  children: [/* @__PURE__ */ jsx("div", {
309
- style: styles.heading,
310
- children: "🚨 An error occurred"
311
- }), /* @__PURE__ */ jsx("div", {
312
- style: styles.message,
313
- children: "Something went wrong. Please try again later."
284
+ style: styles.stackHeader,
285
+ children: "Call Stack"
286
+ }), /* @__PURE__ */ jsxs("div", {
287
+ style: styles.frameList,
288
+ children: [
289
+ visibleFrames.map((frame, i) => /* @__PURE__ */ jsx(StackFrameRow, {
290
+ frame,
291
+ index: i
292
+ }, i)),
293
+ !expanded && hiddenCount > 0 && /* @__PURE__ */ jsxs("button", {
294
+ type: "button",
295
+ onClick: onToggle,
296
+ style: styles.expandBtn,
297
+ children: [
298
+ "Show ",
299
+ hiddenCount,
300
+ " more frames"
301
+ ]
302
+ }),
303
+ expanded && hiddenCount > 0 && /* @__PURE__ */ jsx("button", {
304
+ type: "button",
305
+ onClick: onToggle,
306
+ style: styles.expandBtn,
307
+ children: "Show less"
308
+ })
309
+ ]
310
+ })]
311
+ });
312
+ }
313
+ /**
314
+ * Single stack frame row
315
+ */
316
+ function StackFrameRow({ frame, index }) {
317
+ const isFirst = index === 0;
318
+ const fileName = frame.file.split("/").pop() || frame.file;
319
+ const dirPath = frame.file.substring(0, frame.file.length - fileName.length);
320
+ return /* @__PURE__ */ jsxs("div", {
321
+ style: {
322
+ ...styles.frame,
323
+ ...isFirst ? styles.frameFirst : {}
324
+ },
325
+ children: [/* @__PURE__ */ jsx("div", {
326
+ style: styles.frameIndex,
327
+ children: index + 1
328
+ }), /* @__PURE__ */ jsxs("div", {
329
+ style: styles.frameContent,
330
+ children: [frame.fn && /* @__PURE__ */ jsx("div", {
331
+ style: styles.fnName,
332
+ children: frame.fn
333
+ }), /* @__PURE__ */ jsxs("div", {
334
+ style: styles.filePath,
335
+ children: [
336
+ /* @__PURE__ */ jsx("span", {
337
+ style: styles.dirPath,
338
+ children: dirPath
339
+ }),
340
+ /* @__PURE__ */ jsx("span", {
341
+ style: styles.fileName,
342
+ children: fileName
343
+ }),
344
+ frame.line && /* @__PURE__ */ jsxs("span", {
345
+ style: styles.lineCol,
346
+ children: [
347
+ ":",
348
+ frame.line,
349
+ ":",
350
+ frame.col
351
+ ]
352
+ })
353
+ ]
354
+ })]
314
355
  })]
315
356
  });
357
+ }
358
+ /**
359
+ * Production error view - minimal information
360
+ */
361
+ function ErrorViewerProduction() {
362
+ return /* @__PURE__ */ jsx("div", {
363
+ style: styles.overlay,
364
+ children: /* @__PURE__ */ jsxs("div", {
365
+ style: styles.prodContainer,
366
+ children: [
367
+ /* @__PURE__ */ jsx("div", {
368
+ style: styles.prodIcon,
369
+ children: "!"
370
+ }),
371
+ /* @__PURE__ */ jsx("h1", {
372
+ style: styles.prodTitle,
373
+ children: "Application Error"
374
+ }),
375
+ /* @__PURE__ */ jsx("p", {
376
+ style: styles.prodMessage,
377
+ children: "An unexpected error occurred. Please try again later."
378
+ }),
379
+ /* @__PURE__ */ jsx("button", {
380
+ type: "button",
381
+ onClick: () => window.location.reload(),
382
+ style: styles.prodButton,
383
+ children: "Reload Page"
384
+ })
385
+ ]
386
+ })
387
+ });
388
+ }
389
+ const styles = {
390
+ overlay: {
391
+ position: "fixed",
392
+ inset: 0,
393
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
394
+ display: "flex",
395
+ alignItems: "flex-start",
396
+ justifyContent: "center",
397
+ padding: "40px 20px",
398
+ overflow: "auto",
399
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
400
+ zIndex: 99999
401
+ },
402
+ container: {
403
+ width: "100%",
404
+ maxWidth: "960px",
405
+ backgroundColor: "#1a1a1a",
406
+ borderRadius: "12px",
407
+ overflow: "hidden",
408
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)"
409
+ },
410
+ header: {
411
+ padding: "24px 28px",
412
+ borderBottom: "1px solid #333",
413
+ background: "linear-gradient(to bottom, #1f1f1f, #1a1a1a)"
414
+ },
415
+ headerTop: {
416
+ display: "flex",
417
+ alignItems: "center",
418
+ justifyContent: "space-between",
419
+ marginBottom: "16px"
420
+ },
421
+ badge: {
422
+ display: "inline-block",
423
+ padding: "6px 12px",
424
+ backgroundColor: "#dc2626",
425
+ color: "#fff",
426
+ fontSize: "12px",
427
+ fontWeight: 600,
428
+ borderRadius: "6px",
429
+ letterSpacing: "0.025em"
430
+ },
431
+ copyBtn: {
432
+ padding: "8px 16px",
433
+ backgroundColor: "transparent",
434
+ color: "#888",
435
+ fontSize: "13px",
436
+ fontWeight: 500,
437
+ border: "1px solid #444",
438
+ borderRadius: "6px",
439
+ cursor: "pointer",
440
+ transition: "all 0.15s"
441
+ },
442
+ message: {
443
+ margin: 0,
444
+ fontSize: "20px",
445
+ fontWeight: 500,
446
+ color: "#fff",
447
+ lineHeight: 1.5,
448
+ wordBreak: "break-word"
449
+ },
450
+ stackSection: { padding: "0" },
451
+ stackHeader: {
452
+ padding: "16px 28px",
453
+ fontSize: "11px",
454
+ fontWeight: 600,
455
+ color: "#666",
456
+ textTransform: "uppercase",
457
+ letterSpacing: "0.1em",
458
+ borderBottom: "1px solid #2a2a2a"
459
+ },
460
+ frameList: {
461
+ display: "flex",
462
+ flexDirection: "column"
463
+ },
464
+ frame: {
465
+ display: "flex",
466
+ alignItems: "flex-start",
467
+ padding: "14px 28px",
468
+ borderBottom: "1px solid #252525",
469
+ transition: "background-color 0.15s"
470
+ },
471
+ frameFirst: { backgroundColor: "rgba(220, 38, 38, 0.1)" },
472
+ frameIndex: {
473
+ width: "28px",
474
+ flexShrink: 0,
475
+ fontSize: "12px",
476
+ fontWeight: 500,
477
+ color: "#555",
478
+ fontFamily: "monospace"
479
+ },
480
+ frameContent: {
481
+ flex: 1,
482
+ minWidth: 0
483
+ },
484
+ fnName: {
485
+ fontSize: "14px",
486
+ fontWeight: 500,
487
+ color: "#e5e5e5",
488
+ marginBottom: "4px",
489
+ fontFamily: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace"
490
+ },
491
+ filePath: {
492
+ fontSize: "13px",
493
+ color: "#888",
494
+ fontFamily: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace",
495
+ wordBreak: "break-all"
496
+ },
497
+ dirPath: { color: "#555" },
498
+ fileName: { color: "#0ea5e9" },
499
+ lineCol: { color: "#eab308" },
500
+ expandBtn: {
501
+ padding: "16px 28px",
502
+ backgroundColor: "transparent",
503
+ color: "#666",
504
+ fontSize: "13px",
505
+ fontWeight: 500,
506
+ border: "none",
507
+ borderTop: "1px solid #252525",
508
+ cursor: "pointer",
509
+ textAlign: "left",
510
+ transition: "all 0.15s"
511
+ },
512
+ prodContainer: {
513
+ textAlign: "center",
514
+ padding: "60px 40px",
515
+ backgroundColor: "#1a1a1a",
516
+ borderRadius: "12px",
517
+ maxWidth: "400px"
518
+ },
519
+ prodIcon: {
520
+ width: "64px",
521
+ height: "64px",
522
+ margin: "0 auto 24px",
523
+ backgroundColor: "#dc2626",
524
+ borderRadius: "50%",
525
+ display: "flex",
526
+ alignItems: "center",
527
+ justifyContent: "center",
528
+ fontSize: "32px",
529
+ fontWeight: 700,
530
+ color: "#fff"
531
+ },
532
+ prodTitle: {
533
+ margin: "0 0 12px",
534
+ fontSize: "24px",
535
+ fontWeight: 600,
536
+ color: "#fff"
537
+ },
538
+ prodMessage: {
539
+ margin: "0 0 28px",
540
+ fontSize: "15px",
541
+ color: "#888",
542
+ lineHeight: 1.6
543
+ },
544
+ prodButton: {
545
+ padding: "12px 24px",
546
+ backgroundColor: "#fff",
547
+ color: "#000",
548
+ fontSize: "14px",
549
+ fontWeight: 600,
550
+ border: "none",
551
+ borderRadius: "8px",
552
+ cursor: "pointer"
553
+ }
316
554
  };
317
555
 
318
556
  //#endregion
@@ -478,8 +716,11 @@ var ErrorBoundary_default = ErrorBoundary;
478
716
  * ```
479
717
  */
480
718
  const NestedView = (props) => {
481
- const index = use(RouterLayerContext)?.index ?? 0;
719
+ const routerLayer = use(RouterLayerContext);
720
+ const index = routerLayer?.index ?? 0;
721
+ const onError = routerLayer?.onError;
482
722
  const state = useRouterState();
723
+ const alepha = useAlepha();
483
724
  const [view, setView] = useState(state.layers[index]?.element);
484
725
  const [animation, setAnimation] = useState("");
485
726
  const animationExitDuration = useRef(0);
@@ -541,12 +782,16 @@ const NestedView = (props) => {
541
782
  fallback: props.errorBoundary,
542
783
  children: element
543
784
  });
785
+ const fallback = (error) => {
786
+ const result = onError?.(error, state) ?? /* @__PURE__ */ jsx(ErrorViewer_default, {
787
+ error,
788
+ alepha
789
+ });
790
+ if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
791
+ return result;
792
+ };
544
793
  return /* @__PURE__ */ jsx(ErrorBoundary_default, {
545
- fallback: (error) => {
546
- const result = state.onError(error, state);
547
- if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
548
- return result;
549
- },
794
+ fallback,
550
795
  children: element
551
796
  });
552
797
  };
@@ -774,6 +1019,7 @@ var ReactPageProvider = class {
774
1019
  }
775
1020
  if (!it.error) try {
776
1021
  const element = await this.createElement(it.route, {
1022
+ ...it.route.props ? it.route.props() : {},
777
1023
  ...props,
778
1024
  ...context
779
1025
  });
@@ -815,9 +1061,6 @@ var ReactPageProvider = class {
815
1061
  }
816
1062
  return { state };
817
1063
  }
818
- createRedirectionLayer(redirect) {
819
- return { redirect };
820
- }
821
1064
  getErrorHandler(route) {
822
1065
  if (route.errorHandler) return route.errorHandler;
823
1066
  let parent = route.parent;
@@ -842,7 +1085,7 @@ var ReactPageProvider = class {
842
1085
  }
843
1086
  href(page, params = {}) {
844
1087
  const found = this.pages.find((it) => it.name === page.options.name);
845
- if (!found) throw new Error(`Page ${page.options.name} not found`);
1088
+ if (!found) throw new AlephaError(`Page ${page.options.name} not found`);
846
1089
  let url = found.path ?? "";
847
1090
  let parent = found.parent;
848
1091
  while (parent) {
@@ -861,7 +1104,8 @@ var ReactPageProvider = class {
861
1104
  const element = page.client ? createElement(ClientOnly_default, typeof page.client === "object" ? page.client : {}, view) : view;
862
1105
  return createElement(RouterLayerContext.Provider, { value: {
863
1106
  index,
864
- path
1107
+ path,
1108
+ onError: this.getErrorHandler(page) ?? ((error) => this.renderError(error))
865
1109
  } }, element);
866
1110
  }
867
1111
  configure = $hook({
@@ -940,8 +1184,7 @@ const isPageRoute = (it) => {
940
1184
  //#region ../../src/core/providers/ReactServerProvider.ts
941
1185
  const envSchema$1 = t.object({
942
1186
  REACT_SSR_ENABLED: t.optional(t.boolean()),
943
- REACT_ROOT_ID: t.text({ default: "root" }),
944
- REACT_SERVER_TEMPLATE: t.optional(t.text({ size: "rich" }))
1187
+ REACT_ROOT_ID: t.text({ default: "root" })
945
1188
  });
946
1189
  /**
947
1190
  * React server provider configuration atom
@@ -1018,7 +1261,7 @@ var ReactServerProvider = class {
1018
1261
  }
1019
1262
  });
1020
1263
  get template() {
1021
- return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
1264
+ return this.alepha.store.get("alepha.react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
1022
1265
  }
1023
1266
  async registerPages(templateLoader) {
1024
1267
  const template = await templateLoader();