@askrjs/askr 0.0.30 → 0.0.31
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.
- package/README.md +21 -3
- package/dist/benchmark.js +2 -2
- package/dist/common/vnode.d.ts +1 -0
- package/dist/common/vnode.d.ts.map +1 -1
- package/dist/common/vnode.js +2 -1
- package/dist/common/vnode.js.map +1 -1
- package/dist/components/error-boundary.d.ts +25 -0
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/components/error-boundary.js +114 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/renderer/dom.js +23 -1
- package/dist/renderer/dom.js.map +1 -1
- package/dist/runtime/component.d.ts +5 -0
- package/dist/runtime/component.d.ts.map +1 -1
- package/dist/runtime/component.js.map +1 -1
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/index.js +166 -0
- package/dist/ssr/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ createIsland({ root: document.body, component: Counter });
|
|
|
23
23
|
### Runtime
|
|
24
24
|
|
|
25
25
|
`@askrjs/askr` exports the core runtime primitives: `state()`, `derive()`,
|
|
26
|
-
`selector()`, `resource()`, routing helpers, JSX control-flow
|
|
27
|
-
entrypoints.
|
|
26
|
+
`selector()`, `resource()`, `ErrorBoundary`, routing helpers, JSX control-flow
|
|
27
|
+
helpers, and SSR/SSG entrypoints.
|
|
28
28
|
|
|
29
29
|
### Explicit reactivity
|
|
30
30
|
|
|
@@ -69,7 +69,25 @@ function Data({ id }: { id: string }) {
|
|
|
69
69
|
|
|
70
70
|
if (data.pending) return <div>Loading...</div>;
|
|
71
71
|
if (data.error) return <div>Failed to load</div>;
|
|
72
|
-
|
|
72
|
+
return <div>{data.value.name}</div>;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Developer error boundaries
|
|
77
|
+
|
|
78
|
+
`ErrorBoundary` is the opt-in boundary primitive for render-time failures. It
|
|
79
|
+
renders a visible fallback in development, still logs the underlying error, and
|
|
80
|
+
can reset when your app state changes.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { ErrorBoundary } from '@askrjs/askr';
|
|
84
|
+
|
|
85
|
+
function App() {
|
|
86
|
+
return (
|
|
87
|
+
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
88
|
+
<FlakyView />
|
|
89
|
+
</ErrorBoundary>
|
|
90
|
+
);
|
|
73
91
|
}
|
|
74
92
|
```
|
|
75
93
|
|
package/dist/benchmark.js
CHANGED
|
@@ -10,8 +10,8 @@ import { BenchmarkTable } from "./bench/components/benchmark-table.js";
|
|
|
10
10
|
installRendererBridge();
|
|
11
11
|
const benchmarkMetadata = {
|
|
12
12
|
packageName: "@askrjs/askr",
|
|
13
|
-
packageVersion: "0.0.
|
|
14
|
-
buildLabel: "0.0.
|
|
13
|
+
packageVersion: "0.0.31",
|
|
14
|
+
buildLabel: "0.0.31-local"
|
|
15
15
|
};
|
|
16
16
|
function getBenchmarkMetadata() {
|
|
17
17
|
return { ...benchmarkMetadata };
|
package/dist/common/vnode.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Props } from "./props.js";
|
|
|
2
2
|
import { ForState } from "../runtime/for.js";
|
|
3
3
|
import { ControlBoundaryState } from "../runtime/control.js";
|
|
4
4
|
//#region src/common/vnode.d.ts
|
|
5
|
+
declare const __ERROR_BOUNDARY__: unique symbol;
|
|
5
6
|
interface DOMElement {
|
|
6
7
|
type: string | ((props: Props) => any) | symbol;
|
|
7
8
|
props?: Props;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vnode.d.ts","names":[],"sources":["../../src/common/vnode.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"vnode.d.ts","names":[],"sources":["../../src/common/vnode.ts"],"mappings":";;;;cAUa,kBAAA;AAAA,UAEI,UAAA;EAKf,IAAA,aAAiB,KAAA,EAAO,KAAA;EACxB,KAAA,GAAQ,KAAA;EACR,QAAA,GAAW,KAAA;EACX,GAAA;EAAA,CACC,MAAA,CAAO,QAAA;EACR,aAAA,GAAgB,oBAAA;EAChB,SAAA,GAAY,QAAA;AAAA;AAAA,KAIF,KAAA,GAAQ,UAAA;AAAA,cAIP,gBAAA;AAAA,iBAEG,aAAA,CAAc,IAAA,YAAgB,IAAA,IAAQ,UAAA"}
|
package/dist/common/vnode.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { __CONTROL_BOUNDARY__ } from "./control.js";
|
|
2
2
|
//#region src/common/vnode.ts
|
|
3
|
+
const __ERROR_BOUNDARY__ = Symbol.for("askr.error-boundary");
|
|
3
4
|
const __FOR_BOUNDARY__ = __CONTROL_BOUNDARY__;
|
|
4
5
|
function _isDOMElement(node) {
|
|
5
6
|
return typeof node === "object" && node !== null && "type" in node;
|
|
6
7
|
}
|
|
7
8
|
//#endregion
|
|
8
|
-
export { __FOR_BOUNDARY__, _isDOMElement };
|
|
9
|
+
export { __ERROR_BOUNDARY__, __FOR_BOUNDARY__, _isDOMElement };
|
|
9
10
|
|
|
10
11
|
//# sourceMappingURL=vnode.js.map
|
package/dist/common/vnode.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vnode.js","names":[],"sources":["../../src/common/vnode.ts"],"sourcesContent":["/**\n * Common call contracts: VNode / virtual DOM shapes\n */\n\nimport type { Props } from './props';\nimport type { ControlBoundaryState } from '../runtime/control';\nimport type { ForState } from '../runtime/for';\nexport { __CONTROL_BOUNDARY__ } from './control';\nimport { __CONTROL_BOUNDARY__ } from './control';\n\nexport interface DOMElement {\n // Element `type` can be an intrinsic tag name, a component function, or\n // a special symbol (e.g. `Fragment`). Include `symbol` in the type union\n // so runtime comparisons against `Fragment` are type-safe.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type: string | ((props: Props) => any) | symbol;\n props?: Props;\n children?: VNode[];\n key?: string | number | null;\n [Symbol.iterator]?: never;\n _controlState?: ControlBoundaryState; // Internal: control boundary state\n _forState?: ForState<unknown>; // Deprecated internal alias during migration\n}\n\n// Type for virtual DOM nodes\nexport type VNode = DOMElement | string | number | boolean | null | undefined;\n\n// Backward-compatible internal alias while renderer/runtime migrates away from\n// the old For-only boundary naming.\nexport const __FOR_BOUNDARY__ = __CONTROL_BOUNDARY__;\n\nexport function _isDOMElement(node: unknown): node is DOMElement {\n return typeof node === 'object' && node !== null && 'type' in node;\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"vnode.js","names":[],"sources":["../../src/common/vnode.ts"],"sourcesContent":["/**\n * Common call contracts: VNode / virtual DOM shapes\n */\n\nimport type { Props } from './props';\nimport type { ControlBoundaryState } from '../runtime/control';\nimport type { ForState } from '../runtime/for';\nexport { __CONTROL_BOUNDARY__ } from './control';\nimport { __CONTROL_BOUNDARY__ } from './control';\n\nexport const __ERROR_BOUNDARY__ = Symbol.for('askr.error-boundary');\n\nexport interface DOMElement {\n // Element `type` can be an intrinsic tag name, a component function, or\n // a special symbol (e.g. `Fragment`). Include `symbol` in the type union\n // so runtime comparisons against `Fragment` are type-safe.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type: string | ((props: Props) => any) | symbol;\n props?: Props;\n children?: VNode[];\n key?: string | number | null;\n [Symbol.iterator]?: never;\n _controlState?: ControlBoundaryState; // Internal: control boundary state\n _forState?: ForState<unknown>; // Deprecated internal alias during migration\n}\n\n// Type for virtual DOM nodes\nexport type VNode = DOMElement | string | number | boolean | null | undefined;\n\n// Backward-compatible internal alias while renderer/runtime migrates away from\n// the old For-only boundary naming.\nexport const __FOR_BOUNDARY__ = __CONTROL_BOUNDARY__;\n\nexport function _isDOMElement(node: unknown): node is DOMElement {\n return typeof node === 'object' && node !== null && 'type' in node;\n}\n"],"mappings":";;AAUA,MAAa,qBAAqB,OAAO,IAAI,sBAAsB;AAqBnE,MAAa,mBAAmB;AAEhC,SAAgB,cAAc,MAAmC;AAC/D,QAAO,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Props } from "../common/props.js";
|
|
2
|
+
import { JSXElement } from "../common/jsx.js";
|
|
3
|
+
import { ComponentInstance } from "../runtime/component.js";
|
|
4
|
+
|
|
5
|
+
//#region src/components/error-boundary.d.ts
|
|
6
|
+
type ErrorBoundaryFallbackRender = (error: unknown, reset: () => void) => unknown;
|
|
7
|
+
interface ErrorBoundaryProps extends Props {
|
|
8
|
+
children?: unknown;
|
|
9
|
+
fallback?: unknown | ErrorBoundaryFallbackRender;
|
|
10
|
+
onError?: (error: unknown) => void;
|
|
11
|
+
resetKey?: unknown;
|
|
12
|
+
}
|
|
13
|
+
type ErrorBoundaryState = NonNullable<ComponentInstance['errorBoundaryState']>;
|
|
14
|
+
type ErrorBoundaryVNode = JSXElement & {
|
|
15
|
+
__instance?: ComponentInstance;
|
|
16
|
+
};
|
|
17
|
+
declare function ErrorBoundary(props: ErrorBoundaryProps): JSXElement;
|
|
18
|
+
declare function isErrorBoundaryVNode(value: unknown): value is ErrorBoundaryVNode;
|
|
19
|
+
declare function resolveErrorBoundaryState(vnode: ErrorBoundaryVNode): ErrorBoundaryState | null;
|
|
20
|
+
declare function resolveErrorBoundaryFallback(fallback: ErrorBoundaryProps['fallback'], error: unknown, reset: () => void): unknown;
|
|
21
|
+
declare function createBoundaryReset(instance: ComponentInstance): () => void;
|
|
22
|
+
declare function reportBoundaryError(instance: ComponentInstance, error: unknown, onError?: (error: unknown) => void): void;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { ErrorBoundary, ErrorBoundaryFallbackRender, ErrorBoundaryProps };
|
|
25
|
+
//# sourceMappingURL=error-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-boundary.d.ts","names":[],"sources":["../../src/components/error-boundary.tsx"],"mappings":";;;;;KAUY,2BAAA,IACV,KAAA,WACA,KAAA;AAAA,UAGe,kBAAA,SAA2B,KAAA;EAC1C,QAAA;EACA,QAAA,aAAqB,2BAAA;EACrB,OAAA,IAAW,KAAA;EACX,QAAA;AAAA;AAAA,KAGG,kBAAA,GAAqB,WAAA,CAAY,iBAAA;AAAA,KAEjC,kBAAA,GAAqB,UAAA;EACxB,UAAA,GAAa,iBAAA;AAAA;AAAA,iBA+DC,aAAA,CAAc,KAAA,EAAO,kBAAA,GAAqB,UAAA;AAAA,iBAY1C,oBAAA,CACd,KAAA,YACC,KAAA,IAAS,kBAAA;AAAA,iBAQI,yBAAA,CACd,KAAA,EAAO,kBAAA,GACN,kBAAA;AAAA,iBAIa,4BAAA,CACd,QAAA,EAAU,kBAAA,cACV,KAAA,WACA,KAAA;AAAA,iBAoDc,mBAAA,CAAoB,QAAA,EAAU,iBAAA;AAAA,iBAc9B,mBAAA,CACd,QAAA,EAAU,iBAAA,EACV,KAAA,WACA,OAAA,IAAW,KAAA"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { __ERROR_BOUNDARY__ } from "../common/vnode.js";
|
|
2
|
+
import { isDevelopmentEnvironment } from "../common/env.js";
|
|
3
|
+
import { logger } from "../dev/logger.js";
|
|
4
|
+
import { ELEMENT_TYPE } from "../common/jsx.js";
|
|
5
|
+
import { getCurrentComponentInstance } from "../runtime/component.js";
|
|
6
|
+
//#region src/components/error-boundary.tsx
|
|
7
|
+
function getBoundaryMessage(error) {
|
|
8
|
+
if (error instanceof Error) return error.message || error.name || "Unknown error";
|
|
9
|
+
if (typeof error === "string") return error;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(error);
|
|
12
|
+
} catch {
|
|
13
|
+
return String(error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function ensureBoundaryState(instance, resetKey) {
|
|
17
|
+
const boundaryState = instance.errorBoundaryState ?? (instance.errorBoundaryState = {
|
|
18
|
+
error: null,
|
|
19
|
+
resetKey,
|
|
20
|
+
notified: false
|
|
21
|
+
});
|
|
22
|
+
if (!Object.is(boundaryState.resetKey, resetKey)) {
|
|
23
|
+
boundaryState.error = null;
|
|
24
|
+
boundaryState.resetKey = resetKey;
|
|
25
|
+
boundaryState.notified = false;
|
|
26
|
+
}
|
|
27
|
+
return boundaryState;
|
|
28
|
+
}
|
|
29
|
+
function createBoundaryVNode(instance, props) {
|
|
30
|
+
const key = typeof props.key === "symbol" ? null : props.key ?? null;
|
|
31
|
+
return {
|
|
32
|
+
$$typeof: ELEMENT_TYPE,
|
|
33
|
+
type: __ERROR_BOUNDARY__,
|
|
34
|
+
props: {
|
|
35
|
+
children: props.children,
|
|
36
|
+
fallback: props.fallback,
|
|
37
|
+
onError: props.onError,
|
|
38
|
+
resetKey: props.resetKey
|
|
39
|
+
},
|
|
40
|
+
key,
|
|
41
|
+
__instance: instance
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function ErrorBoundary(props) {
|
|
45
|
+
const instance = getCurrentComponentInstance();
|
|
46
|
+
if (!instance) throw new Error("[Askr] ErrorBoundary() can only be used during component render execution.");
|
|
47
|
+
ensureBoundaryState(instance, props.resetKey);
|
|
48
|
+
return createBoundaryVNode(instance, props);
|
|
49
|
+
}
|
|
50
|
+
function resolveErrorBoundaryFallback(fallback, error, reset) {
|
|
51
|
+
if (typeof fallback === "function") return fallback(error, reset);
|
|
52
|
+
if (fallback !== void 0) return fallback;
|
|
53
|
+
const message = getBoundaryMessage(error);
|
|
54
|
+
const wrapper = document.createElement("div");
|
|
55
|
+
wrapper.setAttribute("role", "alert");
|
|
56
|
+
wrapper.setAttribute("data-askr-error-boundary", "true");
|
|
57
|
+
wrapper.style.boxSizing = "border-box";
|
|
58
|
+
wrapper.style.padding = "1rem";
|
|
59
|
+
wrapper.style.border = "1px solid currentColor";
|
|
60
|
+
wrapper.style.borderRadius = "0.75rem";
|
|
61
|
+
wrapper.style.display = "grid";
|
|
62
|
+
wrapper.style.gap = "0.75rem";
|
|
63
|
+
wrapper.style.maxWidth = "100%";
|
|
64
|
+
const title = document.createElement("strong");
|
|
65
|
+
title.textContent = "Something went wrong while rendering this view.";
|
|
66
|
+
const summary = document.createElement("p");
|
|
67
|
+
summary.textContent = "The app recovered into a visible fallback so the error is not hidden in the console.";
|
|
68
|
+
summary.style.margin = "0";
|
|
69
|
+
const details = document.createElement("details");
|
|
70
|
+
details.open = isDevelopmentEnvironment();
|
|
71
|
+
const detailsSummary = document.createElement("summary");
|
|
72
|
+
detailsSummary.textContent = "Error details";
|
|
73
|
+
const pre = document.createElement("pre");
|
|
74
|
+
pre.textContent = message;
|
|
75
|
+
pre.style.margin = "0";
|
|
76
|
+
pre.style.whiteSpace = "pre-wrap";
|
|
77
|
+
pre.style.wordBreak = "break-word";
|
|
78
|
+
const button = document.createElement("button");
|
|
79
|
+
button.type = "button";
|
|
80
|
+
button.textContent = "Try again";
|
|
81
|
+
button.addEventListener("click", () => reset());
|
|
82
|
+
details.append(detailsSummary, pre);
|
|
83
|
+
wrapper.append(title, summary, details, button);
|
|
84
|
+
return wrapper;
|
|
85
|
+
}
|
|
86
|
+
function createBoundaryReset(instance) {
|
|
87
|
+
return () => {
|
|
88
|
+
const boundaryState = instance.errorBoundaryState;
|
|
89
|
+
if (!boundaryState) return;
|
|
90
|
+
boundaryState.error = null;
|
|
91
|
+
boundaryState.notified = false;
|
|
92
|
+
queueMicrotask(() => {
|
|
93
|
+
instance._enqueueRun?.();
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function reportBoundaryError(instance, error, onError) {
|
|
98
|
+
const boundaryState = instance.errorBoundaryState;
|
|
99
|
+
if (boundaryState && Object.is(boundaryState.error, error) && boundaryState.notified) return;
|
|
100
|
+
if (boundaryState) {
|
|
101
|
+
boundaryState.error = error;
|
|
102
|
+
boundaryState.notified = true;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
onError?.(error);
|
|
106
|
+
} catch (hookError) {
|
|
107
|
+
logger.error("[Askr] ErrorBoundary onError handler threw:", hookError);
|
|
108
|
+
}
|
|
109
|
+
logger.error("[Askr] ErrorBoundary caught render error:", error);
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { ErrorBoundary, createBoundaryReset, reportBoundaryError, resolveErrorBoundaryFallback };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=error-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-boundary.js","names":[],"sources":["../../src/components/error-boundary.tsx"],"sourcesContent":["import type { Props } from '../common/props';\nimport { ELEMENT_TYPE, type JSXElement } from '../common/jsx';\nimport { __ERROR_BOUNDARY__ } from '../common/vnode';\nimport {\n getCurrentComponentInstance,\n type ComponentInstance,\n} from '../runtime/component';\nimport { logger } from '../dev/logger';\nimport { isDevelopmentEnvironment } from '../common/env';\n\nexport type ErrorBoundaryFallbackRender = (\n error: unknown,\n reset: () => void\n) => unknown;\n\nexport interface ErrorBoundaryProps extends Props {\n children?: unknown;\n fallback?: unknown | ErrorBoundaryFallbackRender;\n onError?: (error: unknown) => void;\n resetKey?: unknown;\n}\n\ntype ErrorBoundaryState = NonNullable<ComponentInstance['errorBoundaryState']>;\n\ntype ErrorBoundaryVNode = JSXElement & {\n __instance?: ComponentInstance;\n};\n\nfunction getBoundaryMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message || error.name || 'Unknown error';\n }\n\n if (typeof error === 'string') {\n return error;\n }\n\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n\nfunction ensureBoundaryState(\n instance: ComponentInstance,\n resetKey: unknown\n): ErrorBoundaryState {\n const boundaryState =\n instance.errorBoundaryState ??\n (instance.errorBoundaryState = {\n error: null,\n resetKey,\n notified: false,\n });\n\n if (!Object.is(boundaryState.resetKey, resetKey)) {\n boundaryState.error = null;\n boundaryState.resetKey = resetKey;\n boundaryState.notified = false;\n }\n\n return boundaryState;\n}\n\nfunction createBoundaryVNode(\n instance: ComponentInstance,\n props: ErrorBoundaryProps\n): ErrorBoundaryVNode {\n const key =\n typeof props.key === 'symbol'\n ? null\n : ((props.key ?? null) as string | number | null);\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: __ERROR_BOUNDARY__,\n props: {\n children: props.children,\n fallback: props.fallback,\n onError: props.onError,\n resetKey: props.resetKey,\n },\n key,\n __instance: instance,\n };\n}\n\nexport function ErrorBoundary(props: ErrorBoundaryProps): JSXElement {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n throw new Error(\n '[Askr] ErrorBoundary() can only be used during component render execution.'\n );\n }\n\n ensureBoundaryState(instance, props.resetKey);\n return createBoundaryVNode(instance, props);\n}\n\nexport function isErrorBoundaryVNode(\n value: unknown\n): value is ErrorBoundaryVNode {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as ErrorBoundaryVNode).type === __ERROR_BOUNDARY__\n );\n}\n\nexport function resolveErrorBoundaryState(\n vnode: ErrorBoundaryVNode\n): ErrorBoundaryState | null {\n return vnode.__instance?.errorBoundaryState ?? null;\n}\n\nexport function resolveErrorBoundaryFallback(\n fallback: ErrorBoundaryProps['fallback'],\n error: unknown,\n reset: () => void\n): unknown {\n if (typeof fallback === 'function') {\n return fallback(error, reset);\n }\n\n if (fallback !== undefined) {\n return fallback;\n }\n\n const message = getBoundaryMessage(error);\n const wrapper = document.createElement('div');\n wrapper.setAttribute('role', 'alert');\n wrapper.setAttribute('data-askr-error-boundary', 'true');\n wrapper.style.boxSizing = 'border-box';\n wrapper.style.padding = '1rem';\n wrapper.style.border = '1px solid currentColor';\n wrapper.style.borderRadius = '0.75rem';\n wrapper.style.display = 'grid';\n wrapper.style.gap = '0.75rem';\n wrapper.style.maxWidth = '100%';\n\n const title = document.createElement('strong');\n title.textContent = 'Something went wrong while rendering this view.';\n\n const summary = document.createElement('p');\n summary.textContent =\n 'The app recovered into a visible fallback so the error is not hidden in the console.';\n summary.style.margin = '0';\n\n const details = document.createElement('details');\n details.open = isDevelopmentEnvironment();\n\n const detailsSummary = document.createElement('summary');\n detailsSummary.textContent = 'Error details';\n\n const pre = document.createElement('pre');\n pre.textContent = message;\n pre.style.margin = '0';\n pre.style.whiteSpace = 'pre-wrap';\n pre.style.wordBreak = 'break-word';\n\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = 'Try again';\n button.addEventListener('click', () => reset());\n\n details.append(detailsSummary, pre);\n wrapper.append(title, summary, details, button);\n return wrapper;\n}\n\nexport function createBoundaryReset(instance: ComponentInstance): () => void {\n return () => {\n const boundaryState = instance.errorBoundaryState;\n if (!boundaryState) {\n return;\n }\n boundaryState.error = null;\n boundaryState.notified = false;\n queueMicrotask(() => {\n instance._enqueueRun?.();\n });\n };\n}\n\nexport function reportBoundaryError(\n instance: ComponentInstance,\n error: unknown,\n onError?: (error: unknown) => void\n): void {\n const boundaryState = instance.errorBoundaryState;\n if (\n boundaryState &&\n Object.is(boundaryState.error, error) &&\n boundaryState.notified\n ) {\n return;\n }\n\n if (boundaryState) {\n boundaryState.error = error;\n boundaryState.notified = true;\n }\n\n try {\n onError?.(error);\n } catch (hookError) {\n logger.error('[Askr] ErrorBoundary onError handler threw:', hookError);\n }\n\n logger.error('[Askr] ErrorBoundary caught render error:', error);\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,5 @@ import { ResourceResult, resource } from "./runtime/operations.js";
|
|
|
18
18
|
import { currentRoute, fallback, group, lazy, registerRoutes, resolveRouteRequest, route } from "./router/route.js";
|
|
19
19
|
import { allow, deny, forbidden, notFound, redirect, requireAuth, requirePermission, requireRole, unauthorized } from "./router/policy.js";
|
|
20
20
|
import { Link, LinkProps } from "./components/link.js";
|
|
21
|
-
|
|
21
|
+
import { ErrorBoundary, ErrorBoundaryFallbackRender, ErrorBoundaryProps } from "./components/error-boundary.js";
|
|
22
|
+
export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, Case, type CaseProps, type Context, DefaultPortal, type Derived, ErrorBoundary, type ErrorBoundaryFallbackRender, type ErrorBoundaryProps, For, type ForProps, Fragment, type GroupHelperOptions, type HistoryScrollBehavior, type HydrateSPAConfig, type IslandConfig, type IslandsConfig, Link, type LinkProps, Match, type MatchProps, type NavigateOptions, type NavigationScrollBehavior, Portal, type PortalProps, type Props, type RegisterRoutesOptions, type ResourceResult, type RouteAuthMode, type RouteAuthOptions, type RouteAuthResolver, type RouteAuthState, type RouteComponent, type RouteContext, type RouteDefinition, type RouteManifest, type RouteMatch, type RouteMode, type RouteOptions, type RoutePolicy, type RouteRecord, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSnapshot, type SPAConfig, type ScrollRestorationOptions, type Selector, Show, type ShowProps, Slot, type SlotProps, type State, allow, cleanupApp, createIsland, createIslands, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Fragment } from "./common/jsx.js";
|
|
|
2
2
|
import { defineContext, readContext } from "./runtime/context.js";
|
|
3
3
|
import { getSignal } from "./runtime/component.js";
|
|
4
4
|
import { jsx, jsxs } from "./jsx-runtime.js";
|
|
5
|
+
import { ErrorBoundary } from "./components/error-boundary.js";
|
|
5
6
|
import { installRendererBridge } from "./renderer/index.js";
|
|
6
7
|
import { state } from "./runtime/state.js";
|
|
7
8
|
import { derive } from "./runtime/derive.js";
|
|
@@ -29,6 +30,6 @@ import "./foundations/structures.js";
|
|
|
29
30
|
*/
|
|
30
31
|
installRendererBridge();
|
|
31
32
|
//#endregion
|
|
32
|
-
export { Case, DefaultPortal, For, Fragment, Link, Match, Portal, Show, Slot, allow, cleanupApp, createIsland, createIslands, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
|
33
|
+
export { Case, DefaultPortal, ErrorBoundary, For, Fragment, Link, Match, Portal, Show, Slot, allow, cleanupApp, createIsland, createIslands, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
|
33
34
|
|
|
34
35
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Askr: Actor-backed deterministic UI framework\n *\n * Public API surface — only users should import from here\n */\n\nimport { installRendererBridge } from './renderer';\n\ninstallRendererBridge();\n\n// Runtime primitives\nexport { state } from './runtime/state';\nexport type { State } from './runtime/state';\nexport { derive } from './runtime/derive';\nexport type { Derived } from './runtime/derive';\nexport { getSignal } from './runtime/component';\nexport { selector } from './runtime/selector';\nexport type { Selector } from './runtime/selector';\n\n// Context\nexport { defineContext, readContext } from './runtime/context';\nexport type { Context } from './runtime/context';\n\n// Resources\nexport { resource } from './runtime/operations';\nexport type { ResourceResult } from './runtime/operations';\n\n// App bootstrap\nexport {\n createIsland,\n createIslands,\n createSPA,\n hydrateSPA,\n cleanupApp,\n hasApp,\n} from './boot';\nexport type {\n IslandConfig,\n IslandsConfig,\n SPAConfig,\n HydrateSPAConfig,\n} from './boot';\n\n// Routing\nexport {\n registerRoutes,\n route,\n currentRoute,\n group,\n fallback,\n lazy,\n allow,\n redirect,\n deny,\n unauthorized,\n forbidden,\n notFound,\n requireAuth,\n requireRole,\n requirePermission,\n resolveRouteRequest,\n type RouteSnapshot,\n type RouteMatch,\n type RouteComponent,\n type RouteMode,\n type RouteAuthMode,\n type RouteContext,\n type RoutePolicy,\n type RouteOptions,\n type RouteRecord,\n type RouteManifest,\n type AccessDecision,\n type AccessDenyDecision,\n type AccessRedirectDecision,\n type GroupHelperOptions,\n type RegisterRoutesOptions,\n type RouteDefinition,\n type RouteAuthOptions,\n type RouteAuthResolver,\n type RouteAuthState,\n type RouteRenderResult,\n type RouteRequestOptions,\n type RouteRequestResult,\n} from './router';\nexport { navigate } from './router/navigate';\nexport type {\n NavigateOptions,\n NavigationScrollBehavior,\n HistoryScrollBehavior,\n ScrollRestorationOptions,\n} from './router/navigate';\n\n// Components\nexport { Link } from './components/link';\nexport type { LinkProps } from './components/link';\nexport { Case, For, Match, Show } from './control';\nexport type { CaseProps, ForProps, MatchProps, ShowProps } from './control';\nexport {\n Slot,\n definePortal,\n DefaultPortal,\n Portal,\n} from './foundations/structures';\nexport type { SlotProps, PortalProps } from './foundations/structures';\n\n// Re-export JSX runtime for tsconfig jsxImportSource\nexport { jsx, jsxs, Fragment } from './jsx-runtime';\n\n// Public types\nexport type { Props } from './common/props';\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Askr: Actor-backed deterministic UI framework\n *\n * Public API surface — only users should import from here\n */\n\nimport { installRendererBridge } from './renderer';\n\ninstallRendererBridge();\n\n// Runtime primitives\nexport { state } from './runtime/state';\nexport type { State } from './runtime/state';\nexport { derive } from './runtime/derive';\nexport type { Derived } from './runtime/derive';\nexport { getSignal } from './runtime/component';\nexport { selector } from './runtime/selector';\nexport type { Selector } from './runtime/selector';\n\n// Context\nexport { defineContext, readContext } from './runtime/context';\nexport type { Context } from './runtime/context';\n\n// Resources\nexport { resource } from './runtime/operations';\nexport type { ResourceResult } from './runtime/operations';\n\n// App bootstrap\nexport {\n createIsland,\n createIslands,\n createSPA,\n hydrateSPA,\n cleanupApp,\n hasApp,\n} from './boot';\nexport type {\n IslandConfig,\n IslandsConfig,\n SPAConfig,\n HydrateSPAConfig,\n} from './boot';\n\n// Routing\nexport {\n registerRoutes,\n route,\n currentRoute,\n group,\n fallback,\n lazy,\n allow,\n redirect,\n deny,\n unauthorized,\n forbidden,\n notFound,\n requireAuth,\n requireRole,\n requirePermission,\n resolveRouteRequest,\n type RouteSnapshot,\n type RouteMatch,\n type RouteComponent,\n type RouteMode,\n type RouteAuthMode,\n type RouteContext,\n type RoutePolicy,\n type RouteOptions,\n type RouteRecord,\n type RouteManifest,\n type AccessDecision,\n type AccessDenyDecision,\n type AccessRedirectDecision,\n type GroupHelperOptions,\n type RegisterRoutesOptions,\n type RouteDefinition,\n type RouteAuthOptions,\n type RouteAuthResolver,\n type RouteAuthState,\n type RouteRenderResult,\n type RouteRequestOptions,\n type RouteRequestResult,\n} from './router';\nexport { navigate } from './router/navigate';\nexport type {\n NavigateOptions,\n NavigationScrollBehavior,\n HistoryScrollBehavior,\n ScrollRestorationOptions,\n} from './router/navigate';\n\n// Components\nexport { Link } from './components/link';\nexport type { LinkProps } from './components/link';\nexport { ErrorBoundary } from './components/error-boundary';\nexport type {\n ErrorBoundaryProps,\n ErrorBoundaryFallbackRender,\n} from './components/error-boundary';\nexport { Case, For, Match, Show } from './control';\nexport type { CaseProps, ForProps, MatchProps, ShowProps } from './control';\nexport {\n Slot,\n definePortal,\n DefaultPortal,\n Portal,\n} from './foundations/structures';\nexport type { SlotProps, PortalProps } from './foundations/structures';\n\n// Re-export JSX runtime for tsconfig jsxImportSource\nexport { jsx, jsxs, Fragment } from './jsx-runtime';\n\n// Public types\nexport type { Props } from './common/props';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,uBAAuB"}
|
package/dist/renderer/dom.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __FOR_BOUNDARY__, _isDOMElement } from "../common/vnode.js";
|
|
1
|
+
import { __ERROR_BOUNDARY__, __FOR_BOUNDARY__, _isDOMElement } from "../common/vnode.js";
|
|
2
2
|
import "./types.js";
|
|
3
3
|
import { getRuntimeEnv } from "../common/env.js";
|
|
4
4
|
import { logger } from "../dev/logger.js";
|
|
@@ -21,6 +21,7 @@ import { clearCaseDomUpdateState, clearShowDomUpdateState, evaluateCaseState, ev
|
|
|
21
21
|
import { reconcileKeyedChildren } from "./reconcile.js";
|
|
22
22
|
import { isBulkTextFastPathEligible, performBulkTextReplace } from "./children.js";
|
|
23
23
|
import { createFineGrainedEffect, markFineGrainedEffectsDirtySource } from "../runtime/effect.js";
|
|
24
|
+
import { createBoundaryReset, reportBoundaryError, resolveErrorBoundaryFallback } from "../components/error-boundary.js";
|
|
24
25
|
//#region src/renderer/dom.ts
|
|
25
26
|
const reactivePropRegistry = /* @__PURE__ */ new Set();
|
|
26
27
|
const controlBoundaryOwners = /* @__PURE__ */ new WeakMap();
|
|
@@ -1085,6 +1086,7 @@ function createDOMNode(node, parentNamespace) {
|
|
|
1085
1086
|
if (typeof type === "string") return createIntrinsicElement(node, type, props, parentNamespace);
|
|
1086
1087
|
if (typeof type === "function") return createComponentElement(node, type, props, parentNamespace);
|
|
1087
1088
|
if (type === __FOR_BOUNDARY__) return createForBoundary(node, props, parentNamespace);
|
|
1089
|
+
if (type === __ERROR_BOUNDARY__) return createErrorBoundaryElement(node, props, parentNamespace);
|
|
1088
1090
|
if (typeof type === "symbol" && (type === Fragment || String(type) === "Symbol(Fragment)")) return createFragmentElement(node, props, parentNamespace);
|
|
1089
1091
|
}
|
|
1090
1092
|
return null;
|
|
@@ -1486,6 +1488,26 @@ function createForBoundary(node, props, parentNamespace) {
|
|
|
1486
1488
|
clearControlBoundaryDomUpdateState(controlState);
|
|
1487
1489
|
return fragment;
|
|
1488
1490
|
}
|
|
1491
|
+
function createErrorBoundaryElement(node, props, parentNamespace) {
|
|
1492
|
+
const boundaryState = node.__instance?.errorBoundaryState ?? null;
|
|
1493
|
+
const reset = node.__instance ? createBoundaryReset(node.__instance) : () => {};
|
|
1494
|
+
const fallback = props.fallback;
|
|
1495
|
+
const children = props.children;
|
|
1496
|
+
if (boundaryState?.error != null) {
|
|
1497
|
+
const fallbackValue = resolveErrorBoundaryFallback(fallback, boundaryState.error, reset);
|
|
1498
|
+
if (fallbackValue instanceof Node) return fallbackValue;
|
|
1499
|
+
return createDOMNode(fallbackValue, parentNamespace) ?? document.createComment("");
|
|
1500
|
+
}
|
|
1501
|
+
try {
|
|
1502
|
+
return createDOMNode(children, parentNamespace) ?? document.createComment("");
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
if (node.__instance) reportBoundaryError(node.__instance, error, props.onError);
|
|
1505
|
+
else logger.error("[Askr] ErrorBoundary caught render error:", error);
|
|
1506
|
+
const fallbackValue = resolveErrorBoundaryFallback(fallback, error, reset);
|
|
1507
|
+
if (fallbackValue instanceof Node) return fallbackValue;
|
|
1508
|
+
return createDOMNode(fallbackValue, parentNamespace) ?? document.createComment("");
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1489
1511
|
function syncForItemDom(parent, scope, vnode) {
|
|
1490
1512
|
let dom = scope.dom ?? null;
|
|
1491
1513
|
const parentNamespace = parent.namespaceURI === SVG_NAMESPACE ? SVG_NAMESPACE : void 0;
|