@terra-symposium/boundaries 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +92 -2
- package/dist/components/AstroErrorBoundary.astro +78 -0
- package/dist/components/react.d.ts +64 -0
- package/dist/components/react.d.ts.map +1 -0
- package/dist/components/react.js +81 -0
- package/dist/components/react.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/astro-integration.d.ts +18 -0
- package/dist/lib/astro-integration.d.ts.map +1 -0
- package/dist/lib/astro-integration.js +52 -0
- package/dist/lib/astro-integration.js.map +1 -0
- package/dist/lib/astro.d.ts +21 -0
- package/dist/lib/astro.d.ts.map +1 -0
- package/dist/lib/astro.js +27 -0
- package/dist/lib/astro.js.map +1 -0
- package/dist/lib/index.d.ts +8 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/next.d.ts +22 -0
- package/dist/lib/next.d.ts.map +1 -0
- package/dist/lib/next.js +18 -0
- package/dist/lib/next.js.map +1 -0
- package/package.json +40 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Terra Symposium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Error Boundaries
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared error-page copy and adapters for React, Next.js route boundaries, and Astro status responses.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Not applicable.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
Not applicable.
|
|
12
|
+
|
|
13
|
+
## Get started
|
|
14
|
+
|
|
15
|
+
### Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pnpm add @terra-symposium/boundaries
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
From `standards/main/`, run `pnpm install`, then `pnpm --filter @terra-symposium/boundaries build`. Validate adapter changes with `pnpm --filter @terra-symposium/boundaries test`.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Catch React render failures
|
|
26
|
+
|
|
27
|
+
````tsx
|
|
28
|
+
import { ErrorBoundary } from '@terra-symposium/boundaries/react';
|
|
29
|
+
|
|
30
|
+
## Structure
|
|
31
|
+
|
|
32
|
+
- See [src](src/), [package.json](package.json), and `tests/`.
|
|
33
|
+
|
|
34
|
+
## References
|
|
35
|
+
|
|
36
|
+
### Catch Astro island hydration failures
|
|
37
|
+
Wrap hydrated islands in the shared Astro boundary to show a static fallback if a client module fails before React can render:
|
|
38
|
+
|
|
39
|
+
```astro
|
|
40
|
+
---
|
|
41
|
+
import AstroErrorBoundary from '@terra-symposium/boundaries/astro-boundary';
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<AstroErrorBoundary title="Workspace unavailable" description="Reload to try the workspace again.">
|
|
45
|
+
<Workspace client:load />
|
|
46
|
+
</AstroErrorBoundary>
|
|
47
|
+
````
|
|
48
|
+
|
|
49
|
+
The Astro wrapper listens for Astro's cancelable `astro:hydration-error` event and reveals its fallback without depending on the failed island's JavaScript. It complements the React `ErrorBoundary`; it cannot render a fallback if the server fails before sending HTML.
|
|
50
|
+
|
|
51
|
+
### Other entry points
|
|
52
|
+
|
|
53
|
+
`@terra-symposium/boundaries/astro` exports status-copy helpers for 400, 401, 403, 404, 500, 502, and 503 responses.
|
|
54
|
+
|
|
55
|
+
`@terra-symposium/boundaries/astro-boundary` exports the reusable static Astro island guard.
|
|
56
|
+
|
|
57
|
+
`@terra-symposium/boundaries/next` exports types and `errorDigest(error)` for framework-provided route-error digests.
|
|
58
|
+
|
|
59
|
+
- The package root re-exports all entry-point APIs.
|
|
60
|
+
|
|
61
|
+
See the [package guide](https://standards-docs.netlify.app/error-boundaries/1-react/react-boundaries/) for prop behavior and usage details.
|
|
62
|
+
|
|
63
|
+
## Configuration and security
|
|
64
|
+
|
|
65
|
+
Not applicable.
|
|
66
|
+
|
|
67
|
+
## Development and structure
|
|
68
|
+
|
|
69
|
+
Not applicable.
|
|
70
|
+
|
|
71
|
+
## Validation
|
|
72
|
+
|
|
73
|
+
Not applicable.
|
|
74
|
+
|
|
75
|
+
## Deployment and operations
|
|
76
|
+
|
|
77
|
+
Not applicable.
|
|
78
|
+
|
|
79
|
+
## Data, privacy, and limitations
|
|
80
|
+
|
|
81
|
+
Not applicable.
|
|
82
|
+
|
|
83
|
+
## References
|
|
84
|
+
|
|
85
|
+
Not applicable.
|
|
86
|
+
|
|
87
|
+
## Contributing
|
|
88
|
+
|
|
89
|
+
Not applicable.
|
|
90
|
+
|
|
91
|
+
## Credits and license
|
|
92
|
+
|
|
93
|
+
Not applicable.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Keeps Astro island hydration failures inside a visible, recoverable boundary. */
|
|
3
|
+
type Props = {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
reloadLabel?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const { title = 'This section could not load', description = 'The interactive interface failed to start. The error has been recorded for diagnosis.', reloadLabel = 'Reload page' } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<terra-error-boundary data-terra-error-boundary>
|
|
13
|
+
<slot />
|
|
14
|
+
<section class='terra-error-boundary-fallback' data-terra-hydration-fallback hidden role='alert'>
|
|
15
|
+
<div>
|
|
16
|
+
<h1>{title}</h1>
|
|
17
|
+
<p>{description}</p>
|
|
18
|
+
<a href={Astro.url.href}>{reloadLabel}</a>
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
</terra-error-boundary>
|
|
22
|
+
|
|
23
|
+
<script is:inline>
|
|
24
|
+
(() => {
|
|
25
|
+
document.addEventListener('astro:hydration-error', event => {
|
|
26
|
+
if (event.defaultPrevented) return;
|
|
27
|
+
const island = event.target;
|
|
28
|
+
if (!(island instanceof Element)) return;
|
|
29
|
+
const boundary = island.closest('terra-error-boundary[data-terra-error-boundary]');
|
|
30
|
+
const fallback = boundary?.querySelector('[data-terra-hydration-fallback]');
|
|
31
|
+
if (!boundary || !fallback) return;
|
|
32
|
+
|
|
33
|
+
// Astro emits this cancelable event before logging a failed island import.
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
console.error('[Terra error boundary] island hydration failed', event.detail?.error);
|
|
36
|
+
island.setAttribute('hidden', '');
|
|
37
|
+
fallback.removeAttribute('hidden');
|
|
38
|
+
});
|
|
39
|
+
})();
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
terra-error-boundary {
|
|
44
|
+
display: contents;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.terra-error-boundary-fallback {
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
max-width: 42rem;
|
|
50
|
+
margin: 3rem auto;
|
|
51
|
+
padding: 1.5rem;
|
|
52
|
+
color: var(--foreground, #171717);
|
|
53
|
+
background: var(--background, #fff);
|
|
54
|
+
border: 1px solid var(--border, #d1d5db);
|
|
55
|
+
border-radius: 0.75rem;
|
|
56
|
+
font: inherit;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.terra-error-boundary-fallback h1 {
|
|
60
|
+
margin: 0 0 0.5rem;
|
|
61
|
+
font-size: 1.25rem;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.terra-error-boundary-fallback p {
|
|
66
|
+
margin: 0 0 1rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.terra-error-boundary-fallback a {
|
|
70
|
+
color: var(--primary, #2563eb);
|
|
71
|
+
text-decoration: underline;
|
|
72
|
+
text-underline-offset: 0.2em;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.terra-error-boundary-fallback[hidden] {
|
|
76
|
+
display: none !important;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/components/react.tsx
|
|
3
|
+
* React error boundary and higher-order wrapper implementations.
|
|
4
|
+
*/
|
|
5
|
+
import React, { Component } from 'react';
|
|
6
|
+
/** Receives a render error and boundary metadata for application-owned logging. */
|
|
7
|
+
export type ErrorReporter = (err: Error, report: {
|
|
8
|
+
name: string;
|
|
9
|
+
componentStack: string | null;
|
|
10
|
+
}) => void;
|
|
11
|
+
/** Configuration and child content accepted by the reusable React error boundary. */
|
|
12
|
+
export type ErrorBoundaryProps = {
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
fallback?: (err: Error) => React.ReactNode;
|
|
15
|
+
name?: string;
|
|
16
|
+
resetKey?: string;
|
|
17
|
+
onError?: ErrorReporter;
|
|
18
|
+
};
|
|
19
|
+
/** Internal React state containing the current descendant render error, if any. */
|
|
20
|
+
export type ErrorBoundaryState = {
|
|
21
|
+
error: Error | null;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Wraps a component in a named error boundary without coupling it to application UI.
|
|
25
|
+
*
|
|
26
|
+
* @param ComponentToWrap Component to render inside the error boundary.
|
|
27
|
+
* @param name Stable name reported when the wrapped component fails.
|
|
28
|
+
* @param onErr Optional callback that receives render errors and component metadata.
|
|
29
|
+
*
|
|
30
|
+
* @return A component with the same props that renders through an error boundary.
|
|
31
|
+
*/
|
|
32
|
+
export declare function withErrorBoundary<P extends object>(ComponentToWrap: React.ComponentType<P>, name: string, onErr?: ErrorReporter): React.ComponentType<P>;
|
|
33
|
+
/** Catches render failures while keeping error reporting and fallback UI injectable. */
|
|
34
|
+
export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
35
|
+
state: ErrorBoundaryState;
|
|
36
|
+
/**
|
|
37
|
+
* Stores a descendant render error so the boundary switches to its fallback.
|
|
38
|
+
*
|
|
39
|
+
* @param err Error caught while rendering a child.
|
|
40
|
+
*
|
|
41
|
+
* @return State containing the captured error.
|
|
42
|
+
*/
|
|
43
|
+
static getDerivedStateFromError(err: Error): ErrorBoundaryState;
|
|
44
|
+
/**
|
|
45
|
+
* Reports a captured render error with its component stack when a callback is configured.
|
|
46
|
+
*
|
|
47
|
+
* @param err Error caught while rendering a child.
|
|
48
|
+
* @param info React component stack captured for the failed render.
|
|
49
|
+
*/
|
|
50
|
+
componentDidCatch(err: Error, info: React.ErrorInfo): void;
|
|
51
|
+
/**
|
|
52
|
+
* Clears the captured error when the boundary receives a new reset key.
|
|
53
|
+
*
|
|
54
|
+
* @param prev Props from the previous render, used to detect reset-key changes.
|
|
55
|
+
*/
|
|
56
|
+
componentDidUpdate(prev: ErrorBoundaryProps): void;
|
|
57
|
+
/**
|
|
58
|
+
* Renders the children until an error is captured, then renders fallback content.
|
|
59
|
+
*
|
|
60
|
+
* @return The children, configured fallback, or default alert.
|
|
61
|
+
*/
|
|
62
|
+
render(): React.ReactNode;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/components/react.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMzC,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KAAK,IAAI,CAAC;AAC1G,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,CAAC;AACvK,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG;IAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAAE,CAAC;AAMzD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAiBxJ;AAED,wFAAwF;AACxF,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IACnF,KAAK,EAAE,kBAAkB,CAAmB;IAC5C;;;;;;OAMG;IACH,MAAM,CAAC,wBAAwB,CAAC,GAAG,EAAE,KAAK,GAAG,kBAAkB;IAG/D;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI;IAG1D;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAGlD;;;;OAIG;IACH,MAAM,IAAI,KAAK,CAAC,SAAS;CAKzB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* packages/error-boundaries/src/components/react.tsx
|
|
4
|
+
* React error boundary and higher-order wrapper implementations.
|
|
5
|
+
*/
|
|
6
|
+
/*//////////////////////////////////////////////////
|
|
7
|
+
IMPORTS
|
|
8
|
+
//////////////////////////////////////////////////*/
|
|
9
|
+
// COMPONENTS
|
|
10
|
+
import { Component } from 'react';
|
|
11
|
+
/*//////////////////////////////////////////////////
|
|
12
|
+
HELPERS
|
|
13
|
+
//////////////////////////////////////////////////*/
|
|
14
|
+
/**
|
|
15
|
+
* Wraps a component in a named error boundary without coupling it to application UI.
|
|
16
|
+
*
|
|
17
|
+
* @param ComponentToWrap Component to render inside the error boundary.
|
|
18
|
+
* @param name Stable name reported when the wrapped component fails.
|
|
19
|
+
* @param onErr Optional callback that receives render errors and component metadata.
|
|
20
|
+
*
|
|
21
|
+
* @return A component with the same props that renders through an error boundary.
|
|
22
|
+
*/
|
|
23
|
+
export function withErrorBoundary(ComponentToWrap, name, onErr) {
|
|
24
|
+
/**
|
|
25
|
+
* Renders the wrapped component inside the configured boundary.
|
|
26
|
+
*
|
|
27
|
+
* @param props Props forwarded unchanged to the wrapped component.
|
|
28
|
+
*
|
|
29
|
+
* @return The wrapped component as a React element under the error boundary.
|
|
30
|
+
*/
|
|
31
|
+
function BoundedComponent(props) {
|
|
32
|
+
return (_jsx(ErrorBoundary, { name: name, onError: onErr, children: _jsx(ComponentToWrap, { ...props }) }));
|
|
33
|
+
}
|
|
34
|
+
BoundedComponent.displayName = `withErrorBoundary(${ComponentToWrap.displayName || ComponentToWrap.name || 'Component'})`;
|
|
35
|
+
return BoundedComponent;
|
|
36
|
+
}
|
|
37
|
+
/** Catches render failures while keeping error reporting and fallback UI injectable. */
|
|
38
|
+
export class ErrorBoundary extends Component {
|
|
39
|
+
state = { error: null };
|
|
40
|
+
/**
|
|
41
|
+
* Stores a descendant render error so the boundary switches to its fallback.
|
|
42
|
+
*
|
|
43
|
+
* @param err Error caught while rendering a child.
|
|
44
|
+
*
|
|
45
|
+
* @return State containing the captured error.
|
|
46
|
+
*/
|
|
47
|
+
static getDerivedStateFromError(err) {
|
|
48
|
+
return { error: err };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reports a captured render error with its component stack when a callback is configured.
|
|
52
|
+
*
|
|
53
|
+
* @param err Error caught while rendering a child.
|
|
54
|
+
* @param info React component stack captured for the failed render.
|
|
55
|
+
*/
|
|
56
|
+
componentDidCatch(err, info) {
|
|
57
|
+
this.props.onError?.(err, { name: this.props.name ?? 'Component', componentStack: info.componentStack ?? null });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clears the captured error when the boundary receives a new reset key.
|
|
61
|
+
*
|
|
62
|
+
* @param prev Props from the previous render, used to detect reset-key changes.
|
|
63
|
+
*/
|
|
64
|
+
componentDidUpdate(prev) {
|
|
65
|
+
if (prev.resetKey !== this.props.resetKey && this.state.error)
|
|
66
|
+
this.setState({ error: null });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Renders the children until an error is captured, then renders fallback content.
|
|
70
|
+
*
|
|
71
|
+
* @return The children, configured fallback, or default alert.
|
|
72
|
+
*/
|
|
73
|
+
render() {
|
|
74
|
+
if (!this.state.error)
|
|
75
|
+
return this.props.children;
|
|
76
|
+
if (this.props.fallback)
|
|
77
|
+
return this.props.fallback(this.state.error);
|
|
78
|
+
return _jsx("div", { role: 'alert', children: "An unexpected error occurred while rendering this section." });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/components/react.tsx"],"names":[],"mappings":";AAAA;;;GAGG;AAEH;;oDAEoD;AAEpD,aAAa;AACb,OAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAazC;;oDAEoD;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAmB,eAAuC,EAAE,IAAY,EAAE,KAAqB;IAC/H;;;;;;OAMG;IACH,SAAS,gBAAgB,CAAC,KAAQ;QACjC,OAAO,CACN,KAAC,aAAa,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,YACxC,KAAC,eAAe,OAAK,KAAK,GAAI,GACf,CAChB,CAAC;IACH,CAAC;IACD,gBAAgB,CAAC,WAAW,GAAG,qBAAqB,eAAe,CAAC,WAAW,IAAI,eAAe,CAAC,IAAI,IAAI,WAAW,GAAG,CAAC;IAC1H,OAAO,gBAAgB,CAAC;AACzB,CAAC;AAED,wFAAwF;AACxF,MAAM,OAAO,aAAc,SAAQ,SAAiD;IACnF,KAAK,GAAuB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC5C;;;;;;OAMG;IACH,MAAM,CAAC,wBAAwB,CAAC,GAAU;QACzC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACvB,CAAC;IACD;;;;;OAKG;IACH,iBAAiB,CAAC,GAAU,EAAE,IAAqB;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI,EAAE,CAAC,CAAC;IAClH,CAAC;IACD;;;;OAIG;IACH,kBAAkB,CAAC,IAAwB;QAC1C,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD;;;;OAIG;IACH,MAAM;QACL,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAClD,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO,cAAK,IAAI,EAAC,OAAO,2EAAiE,CAAC;IAC3F,CAAC;CACD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/lib/astro-integration.ts
|
|
3
|
+
* Installs a visible page-local fallback for Astro island hydration failures.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Adds a shared accessible fallback before Astro starts hydrating any page islands.
|
|
7
|
+
*
|
|
8
|
+
* @return An Astro integration that handles each failed island without replacing the page.
|
|
9
|
+
*/
|
|
10
|
+
export declare function terraErrorBoundaryIntegration(): {
|
|
11
|
+
name: string;
|
|
12
|
+
hooks: {
|
|
13
|
+
'astro:config:setup': ({ injectScript }: {
|
|
14
|
+
injectScript: (stage: "before-hydration", content: string) => void;
|
|
15
|
+
}) => void;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=astro-integration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro-integration.d.ts","sourceRoot":"","sources":["../../src/lib/astro-integration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH;;;;GAIG;AACH,wBAAgB,6BAA6B;;;iDAID;YAAE,YAAY,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;SAAE;;EAkCjH"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/lib/astro-integration.ts
|
|
3
|
+
* Installs a visible page-local fallback for Astro island hydration failures.
|
|
4
|
+
*/
|
|
5
|
+
/*//////////////////////////////////////////////////
|
|
6
|
+
CONFIG
|
|
7
|
+
//////////////////////////////////////////////////*/
|
|
8
|
+
/*//////////////////////////////////////////////////
|
|
9
|
+
FUNCTIONS
|
|
10
|
+
//////////////////////////////////////////////////*/
|
|
11
|
+
/**
|
|
12
|
+
* Adds a shared accessible fallback before Astro starts hydrating any page islands.
|
|
13
|
+
*
|
|
14
|
+
* @return An Astro integration that handles each failed island without replacing the page.
|
|
15
|
+
*/
|
|
16
|
+
export function terraErrorBoundaryIntegration() {
|
|
17
|
+
return {
|
|
18
|
+
name: '@terra-symposium/boundaries',
|
|
19
|
+
hooks: {
|
|
20
|
+
'astro:config:setup': ({ injectScript }) => {
|
|
21
|
+
injectScript('before-hydration', `(() => {
|
|
22
|
+
if (window.__terraHydrationBoundaryInstalled) return;
|
|
23
|
+
window.__terraHydrationBoundaryInstalled = true;
|
|
24
|
+
document.addEventListener('astro:hydration-error', (event) => {
|
|
25
|
+
if (event.defaultPrevented) return;
|
|
26
|
+
const island = event.target;
|
|
27
|
+
if (!(island instanceof HTMLElement) || !island.isConnected) return;
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
island.hidden = true;
|
|
30
|
+
const fallback = document.createElement('section');
|
|
31
|
+
fallback.setAttribute('role', 'alert');
|
|
32
|
+
fallback.setAttribute('data-terra-hydration-fallback', '');
|
|
33
|
+
fallback.style.cssText = 'box-sizing:border-box;max-width:42rem;margin:3rem auto;padding:1.5rem;color:var(--foreground,#171717);background:var(--background,#fff);border:1px solid var(--border,#d1d5db);border-radius:.75rem;font:inherit';
|
|
34
|
+
const heading = document.createElement('h1');
|
|
35
|
+
heading.textContent = 'Interactive section could not load';
|
|
36
|
+
heading.style.cssText = 'margin:0 0 .5rem;font-size:1.25rem;font-weight:600';
|
|
37
|
+
const message = document.createElement('p');
|
|
38
|
+
message.textContent = 'The rest of this page is still available. Reload to try this section again.';
|
|
39
|
+
const reload = document.createElement('a');
|
|
40
|
+
reload.href = window.location.href;
|
|
41
|
+
reload.textContent = 'Reload page';
|
|
42
|
+
reload.style.cssText = 'color:var(--primary,#2563eb);text-decoration:underline;text-underline-offset:.2em';
|
|
43
|
+
fallback.append(heading, message, reload);
|
|
44
|
+
island.insertAdjacentElement('afterend', fallback);
|
|
45
|
+
console.error('[Terra error boundary] island hydration failed', event.detail?.error);
|
|
46
|
+
});
|
|
47
|
+
})();`);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=astro-integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro-integration.js","sourceRoot":"","sources":["../../src/lib/astro-integration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;oDAEoD;AAEpD;;oDAEoD;AAEpD;;;;GAIG;AACH,MAAM,UAAU,6BAA6B;IAC5C,OAAO;QACN,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE;YACN,oBAAoB,EAAE,CAAC,EAAE,YAAY,EAA0E,EAAE,EAAE;gBAClH,YAAY,CACX,kBAAkB,EAClB;;;;;;;;;;;;;;;;;;;;;;;;;;MA0BC,CACD,CAAC;YACH,CAAC;SACD;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/lib/astro.ts
|
|
3
|
+
* Shared status-code copy for Astro error responses.
|
|
4
|
+
*/
|
|
5
|
+
/** Supported HTTP statuses with shared user-facing error-page copy. */
|
|
6
|
+
export type ErrorStatus = 400 | 401 | 403 | 404 | 500 | 502 | 503;
|
|
7
|
+
/** Heading and description shown for one supported error status. */
|
|
8
|
+
export type ErrorStatusCopy = {
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const ERROR_STATUS_COPY: Readonly<Record<ErrorStatus, ErrorStatusCopy>>;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves unknown status values to the safe internal-server-error response.
|
|
15
|
+
*
|
|
16
|
+
* @param status HTTP status code to resolve.
|
|
17
|
+
*
|
|
18
|
+
* @return Shared copy for a supported status, or generic copy for an unknown value.
|
|
19
|
+
*/
|
|
20
|
+
export declare function errorStatusCopy(status: number): ErrorStatusCopy;
|
|
21
|
+
//# sourceMappingURL=astro.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../src/lib/astro.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,uEAAuE;AACvE,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAClE,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AACrE,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,eAAe,CAAC,CAQ3E,CAAC;AAMH;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAE/D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/lib/astro.ts
|
|
3
|
+
* Shared status-code copy for Astro error responses.
|
|
4
|
+
*/
|
|
5
|
+
export const ERROR_STATUS_COPY = Object.freeze({
|
|
6
|
+
400: { title: 'Bad Request', description: 'The server could not understand the request due to invalid syntax.' },
|
|
7
|
+
401: { title: 'Unauthorized', description: 'You need to authenticate to access this resource.' },
|
|
8
|
+
403: { title: 'Forbidden', description: 'You do not have permission to access this resource.' },
|
|
9
|
+
404: { title: 'Not Found', description: 'The requested page or resource could not be found.' },
|
|
10
|
+
500: { title: 'Internal Server Error', description: 'The server encountered an unexpected condition preventing it from fulfilling the request.' },
|
|
11
|
+
502: { title: 'Bad Gateway', description: 'The server received an invalid response from an upstream server.' },
|
|
12
|
+
503: { title: 'Service Unavailable', description: 'The server is currently unable to handle the request due to temporary overload or maintenance.' },
|
|
13
|
+
});
|
|
14
|
+
/*//////////////////////////////////////////////////
|
|
15
|
+
FUNCTIONS
|
|
16
|
+
//////////////////////////////////////////////////*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolves unknown status values to the safe internal-server-error response.
|
|
19
|
+
*
|
|
20
|
+
* @param status HTTP status code to resolve.
|
|
21
|
+
*
|
|
22
|
+
* @return Shared copy for a supported status, or generic copy for an unknown value.
|
|
23
|
+
*/
|
|
24
|
+
export function errorStatusCopy(status) {
|
|
25
|
+
return ERROR_STATUS_COPY[status] ?? { title: 'Unknown Error', description: 'Something went wrong.' };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=astro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.js","sourceRoot":"","sources":["../../src/lib/astro.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,CAAC,MAAM,iBAAiB,GAAmD,MAAM,CAAC,MAAM,CAAC;IAC9F,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,oEAAoE,EAAE;IAChH,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,mDAAmD,EAAE;IAChG,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,qDAAqD,EAAE;IAC/F,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,oDAAoD,EAAE;IAC9F,GAAG,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE,WAAW,EAAE,2FAA2F,EAAE;IACjJ,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,kEAAkE,EAAE;IAC9G,GAAG,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,gGAAgG,EAAE;CACpJ,CAAC,CAAC;AAEH;;oDAEoD;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC7C,OAAO,iBAAiB,CAAC,MAAqB,CAAC,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;AACrH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/lib/next.ts
|
|
3
|
+
* Shared error helpers for Next.js route boundaries.
|
|
4
|
+
*/
|
|
5
|
+
/** Next.js route error with the optional framework-provided diagnostic digest. */
|
|
6
|
+
export type NextRouteError = Error & {
|
|
7
|
+
digest?: string;
|
|
8
|
+
};
|
|
9
|
+
/** Props used by a Next.js route error component that can retry rendering. */
|
|
10
|
+
export type NextRouteErrorProps = {
|
|
11
|
+
error: NextRouteError;
|
|
12
|
+
reset: () => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Returns the framework-provided digest without exposing an error message as a public identifier.
|
|
16
|
+
*
|
|
17
|
+
* @param err Route error whose framework digest should be read.
|
|
18
|
+
*
|
|
19
|
+
* @return The framework digest, when present, without exposing the error message.
|
|
20
|
+
*/
|
|
21
|
+
export declare function errorDigest(err: NextRouteError): string | undefined;
|
|
22
|
+
//# sourceMappingURL=next.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/lib/next.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,kFAAkF;AAClF,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,8EAA8E;AAC9E,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AAM/E;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAEnE"}
|
package/dist/lib/next.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/error-boundaries/src/lib/next.ts
|
|
3
|
+
* Shared error helpers for Next.js route boundaries.
|
|
4
|
+
*/
|
|
5
|
+
/*//////////////////////////////////////////////////
|
|
6
|
+
FUNCTIONS
|
|
7
|
+
//////////////////////////////////////////////////*/
|
|
8
|
+
/**
|
|
9
|
+
* Returns the framework-provided digest without exposing an error message as a public identifier.
|
|
10
|
+
*
|
|
11
|
+
* @param err Route error whose framework digest should be read.
|
|
12
|
+
*
|
|
13
|
+
* @return The framework digest, when present, without exposing the error message.
|
|
14
|
+
*/
|
|
15
|
+
export function errorDigest(err) {
|
|
16
|
+
return err.digest;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=next.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/lib/next.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;oDAEoD;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAmB;IAC9C,OAAO,GAAG,CAAC,MAAM,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terra-symposium/boundaries",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Framework-neutral React error boundaries for Terra Symposium applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"registry": "https://registry.npmjs.org",
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/Terra-Symposium/standards.git"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./react": "./dist/components/react.js",
|
|
19
|
+
"./astro": "./dist/lib/astro.js",
|
|
20
|
+
"./astro-integration": "./dist/lib/astro-integration.js",
|
|
21
|
+
"./astro-boundary": "./dist/components/AstroErrorBoundary.astro",
|
|
22
|
+
"./next": "./dist/lib/next.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=18",
|
|
29
|
+
"react-dom": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^19.3.0",
|
|
33
|
+
"react": "^19.3.1",
|
|
34
|
+
"typescript": "^6.0.3"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"quality": "node \"$(git rev-parse --show-toplevel)/scripts/quality-runner.mjs\" quality",
|
|
38
|
+
"quality:all": "node \"$(git rev-parse --show-toplevel)/scripts/quality-runner.mjs\" quality --all",
|
|
39
|
+
"build": "tsc -p config/tsconfig.json && mkdir -p dist/components && cp src/components/AstroErrorBoundary.astro dist/components/AstroErrorBoundary.astro",
|
|
40
|
+
"test": "npm run build && mkdir -p tests/results && node --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=tests/results/junit.xml tests/*.test.mjs",
|
|
41
|
+
"typecheck": "tsc --noEmit -p config/tsconfig.json"
|
|
42
|
+
}
|
|
6
43
|
}
|