@codeserk/forge-stats-react 0.0.0-stage → 0.4.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 +52 -2
- package/dist/index.cjs +118 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +43 -0
- package/dist/index.d.mts +43 -0
- package/dist/index.mjs +109 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 codeserk
|
|
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,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @codeserk/forge-stats-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React plugin for [`@codeserk/forge-stats`](../stats/README.md). It re-exports the whole core client, so a React app imports this package alone. The core is a peer dependency, installed with it by npm 7+, so the app always has a single client. Uncaught errors already reach `captureGlobalErrors`; the React tools add their component stack and the errors React error boundaries catch. Every tool is optional, pick the ones you need.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @codeserk/forge-stats-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Create the plugin, pass it to `init`, then use the tools you need. They report through the client `init` created:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { captureGlobalErrors, init, reactPlugin } from '@codeserk/forge-stats-react'
|
|
17
|
+
|
|
18
|
+
export const react = reactPlugin()
|
|
19
|
+
|
|
20
|
+
init({ sdk: 'YOUR_SDK_KEY', appVersion: '1.2.3', plugins: [react] })
|
|
21
|
+
captureGlobalErrors()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Error boundary
|
|
25
|
+
|
|
26
|
+
Any React version. Tracks the errors of its children as handled and renders the fallback:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<react.ErrorBoundary fallback={(error) => <Oops message={error.message} />}>
|
|
30
|
+
<App />
|
|
31
|
+
</react.ErrorBoundary>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Root error handlers
|
|
35
|
+
|
|
36
|
+
React 19. Tracks every uncaught error and every error a boundary caught, logs it with `console.error` as React does, then calls your handler:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
createRoot(container, {
|
|
40
|
+
onUncaughtError: react.errorHandler(),
|
|
41
|
+
onCaughtError: react.errorHandler(showErrorToast),
|
|
42
|
+
}).render(<App />)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Your own boundary
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
49
|
+
react.captureError(error, errorInfo)
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A tool used before the plugin is set up by `init` only warns.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let react = require("react");
|
|
3
|
+
//#region src/capture.const.ts
|
|
4
|
+
const REACT_FRAMEWORK = "react";
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/capture.ts
|
|
7
|
+
/**
|
|
8
|
+
* Returns a capture that tracks the error with its component stack, or only warns before the plugin is set up.
|
|
9
|
+
* @param getClient returns undefined until the plugin is set up
|
|
10
|
+
* @returns the capture
|
|
11
|
+
*/
|
|
12
|
+
function createReactCapture(getClient) {
|
|
13
|
+
return (thrown, errorInfo, handled = true) => {
|
|
14
|
+
const client = getClient();
|
|
15
|
+
if (!client) {
|
|
16
|
+
console.warn("[forge-stats] The react plugin is not set up, add it to the plugins of init");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
20
|
+
client.trackError(error, {
|
|
21
|
+
handled,
|
|
22
|
+
componentStack: errorInfo.componentStack ?? void 0,
|
|
23
|
+
framework: REACT_FRAMEWORK
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/error-boundary.ts
|
|
29
|
+
/**
|
|
30
|
+
* Returns an error boundary that tracks the errors of its children and renders the fallback.
|
|
31
|
+
* @param capture
|
|
32
|
+
* @returns the error boundary component
|
|
33
|
+
*/
|
|
34
|
+
function createErrorBoundary(capture) {
|
|
35
|
+
return class ForgeErrorBoundary extends react.Component {
|
|
36
|
+
constructor(..._args) {
|
|
37
|
+
super(..._args);
|
|
38
|
+
this.state = {};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns the state with what the children threw, as an error.
|
|
42
|
+
* @param thrown
|
|
43
|
+
* @returns the state
|
|
44
|
+
*/
|
|
45
|
+
static getDerivedStateFromError(thrown) {
|
|
46
|
+
return { error: thrown instanceof Error ? thrown : new Error(String(thrown)) };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Tracks the error as handled, then calls `onError`.
|
|
50
|
+
* @param error
|
|
51
|
+
* @param errorInfo
|
|
52
|
+
*/
|
|
53
|
+
componentDidCatch(error, errorInfo) {
|
|
54
|
+
capture(error, errorInfo);
|
|
55
|
+
this.props.onError?.(error, errorInfo);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns the children, or the fallback once they threw.
|
|
59
|
+
* @returns the children or the fallback
|
|
60
|
+
*/
|
|
61
|
+
render() {
|
|
62
|
+
const { error } = this.state;
|
|
63
|
+
if (!error) return this.props.children ?? null;
|
|
64
|
+
const { fallback } = this.props;
|
|
65
|
+
return typeof fallback === "function" ? fallback(error) : fallback ?? null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/error-handler.ts
|
|
71
|
+
/**
|
|
72
|
+
* Returns a `createRoot` error handler that tracks the error, logs it as React does, then calls the callback.
|
|
73
|
+
* @param capture
|
|
74
|
+
* @param callback
|
|
75
|
+
* @returns the root error handler
|
|
76
|
+
*/
|
|
77
|
+
function createErrorHandler(capture, callback) {
|
|
78
|
+
return (error, errorInfo) => {
|
|
79
|
+
capture(error, errorInfo, errorInfo.errorBoundary !== void 0);
|
|
80
|
+
console.error(error);
|
|
81
|
+
callback?.(error, errorInfo);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/plugin.const.ts
|
|
86
|
+
const REACT_PLUGIN_NAME = "react";
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/plugin.ts
|
|
89
|
+
/**
|
|
90
|
+
* Returns the React plugin for `init`; its tools track errors through the client `init` created.
|
|
91
|
+
* @returns the plugin
|
|
92
|
+
*/
|
|
93
|
+
function reactPlugin() {
|
|
94
|
+
let client;
|
|
95
|
+
const captureError = createReactCapture(() => client);
|
|
96
|
+
return {
|
|
97
|
+
name: REACT_PLUGIN_NAME,
|
|
98
|
+
setup: (pluginClient) => {
|
|
99
|
+
client = pluginClient;
|
|
100
|
+
},
|
|
101
|
+
ErrorBoundary: createErrorBoundary(captureError),
|
|
102
|
+
errorHandler: (callback) => createErrorHandler(captureError, callback),
|
|
103
|
+
captureError
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
exports.reactPlugin = reactPlugin;
|
|
108
|
+
var _codeserk_forge_stats = require("@codeserk/forge-stats");
|
|
109
|
+
Object.keys(_codeserk_forge_stats).forEach(function(k) {
|
|
110
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
get: function() {
|
|
113
|
+
return _codeserk_forge_stats[k];
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Component"],"sources":["../src/capture.const.ts","../src/capture.ts","../src/error-boundary.ts","../src/error-handler.ts","../src/plugin.const.ts","../src/plugin.ts"],"sourcesContent":["export const REACT_FRAMEWORK = 'react'\n","import type { Client } from '@codeserk/forge-stats'\n\nimport { REACT_FRAMEWORK } from './capture.const'\nimport type { ReactCapture } from './capture.types'\n\n/**\n * Returns a capture that tracks the error with its component stack, or only warns before the plugin is set up.\n * @param getClient returns undefined until the plugin is set up\n * @returns the capture\n */\nexport function createReactCapture(getClient: () => Client | undefined): ReactCapture {\n return (thrown, errorInfo, handled = true) => {\n const client = getClient()\n if (!client) {\n console.warn('[forge-stats] The react plugin is not set up, add it to the plugins of init')\n return\n }\n\n const error = thrown instanceof Error ? thrown : new Error(String(thrown))\n client.trackError(error, {\n handled,\n componentStack: errorInfo.componentStack ?? undefined,\n framework: REACT_FRAMEWORK,\n })\n }\n}\n","import { Component, type ComponentClass, type ErrorInfo, type ReactNode } from 'react'\n\nimport type { ReactCapture } from './capture.types'\nimport type { ForgeErrorBoundaryProps, ForgeErrorBoundaryState } from './error-boundary.types'\n\n/**\n * Returns an error boundary that tracks the errors of its children and renders the fallback.\n * @param capture\n * @returns the error boundary component\n */\nexport function createErrorBoundary(\n capture: ReactCapture,\n): ComponentClass<ForgeErrorBoundaryProps, ForgeErrorBoundaryState> {\n return class ForgeErrorBoundary extends Component<\n ForgeErrorBoundaryProps,\n ForgeErrorBoundaryState\n > {\n override state: ForgeErrorBoundaryState = {}\n\n /**\n * Returns the state with what the children threw, as an error.\n * @param thrown\n * @returns the state\n */\n static getDerivedStateFromError(thrown: unknown): ForgeErrorBoundaryState {\n return { error: thrown instanceof Error ? thrown : new Error(String(thrown)) }\n }\n\n /**\n * Tracks the error as handled, then calls `onError`.\n * @param error\n * @param errorInfo\n */\n override componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n capture(error, errorInfo)\n this.props.onError?.(error, errorInfo)\n }\n\n /**\n * Returns the children, or the fallback once they threw.\n * @returns the children or the fallback\n */\n override render(): ReactNode {\n const { error } = this.state\n if (!error) {\n return this.props.children ?? null\n }\n\n const { fallback } = this.props\n\n return typeof fallback === 'function' ? fallback(error) : (fallback ?? null)\n }\n }\n}\n","import type { ReactCapture } from './capture.types'\nimport type { ForgeErrorHandler } from './error-handler.types'\n\n/**\n * Returns a `createRoot` error handler that tracks the error, logs it as React does, then calls the callback.\n * @param capture\n * @param callback\n * @returns the root error handler\n */\nexport function createErrorHandler(\n capture: ReactCapture,\n callback?: ForgeErrorHandler,\n): ForgeErrorHandler {\n return (error, errorInfo) => {\n capture(error, errorInfo, errorInfo.errorBoundary !== undefined)\n console.error(error)\n callback?.(error, errorInfo)\n }\n}\n","export const REACT_PLUGIN_NAME = 'react'\n","import type { Client } from '@codeserk/forge-stats'\n\nimport { createReactCapture } from './capture'\nimport { createErrorBoundary } from './error-boundary'\nimport { createErrorHandler } from './error-handler'\nimport { REACT_PLUGIN_NAME } from './plugin.const'\nimport type { ReactPlugin } from './plugin.types'\n\n/**\n * Returns the React plugin for `init`; its tools track errors through the client `init` created.\n * @returns the plugin\n */\nexport function reactPlugin(): ReactPlugin {\n let client: Client | undefined\n const captureError = createReactCapture(() => client)\n\n return {\n name: REACT_PLUGIN_NAME,\n setup: (pluginClient) => {\n client = pluginClient\n },\n ErrorBoundary: createErrorBoundary(captureError),\n errorHandler: (callback) => createErrorHandler(captureError, callback),\n captureError,\n }\n}\n"],"mappings":";;;AAAA,MAAa,kBAAkB;;;;;;;;ACU/B,SAAgB,mBAAmB,WAAmD;AACpF,SAAQ,QAAQ,WAAW,UAAU,SAAS;EAC5C,MAAM,SAAS,WAAW;AAC1B,MAAI,CAAC,QAAQ;AACX,WAAQ,KAAK,8EAA8E;AAC3F;;EAGF,MAAM,QAAQ,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,OAAO,CAAC;AAC1E,SAAO,WAAW,OAAO;GACvB;GACA,gBAAgB,UAAU,kBAAkB,KAAA;GAC5C,WAAW;GACZ,CAAC;;;;;;;;;;ACbN,SAAgB,oBACd,SACkE;AAClE,QAAO,MAAM,2BAA2BA,MAAAA,UAGtC;;;gBAC0C,EAAE;;;;;;;EAO5C,OAAO,yBAAyB,QAA0C;AACxE,UAAO,EAAE,OAAO,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,OAAO,CAAC,EAAE;;;;;;;EAQhF,kBAA2B,OAAc,WAA4B;AACnE,WAAQ,OAAO,UAAU;AACzB,QAAK,MAAM,UAAU,OAAO,UAAU;;;;;;EAOxC,SAA6B;GAC3B,MAAM,EAAE,UAAU,KAAK;AACvB,OAAI,CAAC,MACH,QAAO,KAAK,MAAM,YAAY;GAGhC,MAAM,EAAE,aAAa,KAAK;AAE1B,UAAO,OAAO,aAAa,aAAa,SAAS,MAAM,GAAI,YAAY;;;;;;;;;;;;ACzC7E,SAAgB,mBACd,SACA,UACmB;AACnB,SAAQ,OAAO,cAAc;AAC3B,UAAQ,OAAO,WAAW,UAAU,kBAAkB,KAAA,EAAU;AAChE,UAAQ,MAAM,MAAM;AACpB,aAAW,OAAO,UAAU;;;;;AChBhC,MAAa,oBAAoB;;;;;;;ACYjC,SAAgB,cAA2B;CACzC,IAAI;CACJ,MAAM,eAAe,yBAAyB,OAAO;AAErD,QAAO;EACL,MAAM;EACN,QAAQ,iBAAiB;AACvB,YAAS;;EAEX,eAAe,oBAAoB,aAAa;EAChD,eAAe,aAAa,mBAAmB,cAAc,SAAS;EACtE;EACD"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { StatsPlugin } from "@codeserk/forge-stats";
|
|
2
|
+
import { ComponentClass, ErrorInfo, ReactNode } from "react";
|
|
3
|
+
export * from "@codeserk/forge-stats";
|
|
4
|
+
|
|
5
|
+
//#region src/capture.types.d.ts
|
|
6
|
+
interface ReactErrorInfo {
|
|
7
|
+
readonly componentStack?: string | null;
|
|
8
|
+
/** Only set for errors a boundary caught. */
|
|
9
|
+
readonly errorBoundary?: unknown;
|
|
10
|
+
}
|
|
11
|
+
type ReactCapture = (thrown: unknown, errorInfo: ReactErrorInfo, handled?: boolean) => void;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/error-boundary.types.d.ts
|
|
14
|
+
interface ForgeErrorBoundaryProps {
|
|
15
|
+
readonly children?: ReactNode;
|
|
16
|
+
/** Rendered instead of the children once they threw, nothing when absent. */
|
|
17
|
+
readonly fallback?: ReactNode | ((error: Error) => ReactNode);
|
|
18
|
+
/** Called after the error is tracked. */
|
|
19
|
+
readonly onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
20
|
+
}
|
|
21
|
+
interface ForgeErrorBoundaryState {
|
|
22
|
+
readonly error?: Error;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/error-handler.types.d.ts
|
|
26
|
+
type ForgeErrorHandler = (error: unknown, errorInfo: ReactErrorInfo) => void;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/plugin.types.d.ts
|
|
29
|
+
interface ReactPlugin extends StatsPlugin {
|
|
30
|
+
readonly ErrorBoundary: ComponentClass<ForgeErrorBoundaryProps, ForgeErrorBoundaryState>;
|
|
31
|
+
errorHandler(callback?: ForgeErrorHandler): ForgeErrorHandler;
|
|
32
|
+
readonly captureError: ReactCapture;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/plugin.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Returns the React plugin for `init`; its tools track errors through the client `init` created.
|
|
38
|
+
* @returns the plugin
|
|
39
|
+
*/
|
|
40
|
+
declare function reactPlugin(): ReactPlugin;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { type ForgeErrorBoundaryProps, type ForgeErrorBoundaryState, type ForgeErrorHandler, type ReactCapture, type ReactErrorInfo, type ReactPlugin, reactPlugin };
|
|
43
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { StatsPlugin } from "@codeserk/forge-stats";
|
|
2
|
+
import { ComponentClass, ErrorInfo, ReactNode } from "react";
|
|
3
|
+
export * from "@codeserk/forge-stats";
|
|
4
|
+
|
|
5
|
+
//#region src/capture.types.d.ts
|
|
6
|
+
interface ReactErrorInfo {
|
|
7
|
+
readonly componentStack?: string | null;
|
|
8
|
+
/** Only set for errors a boundary caught. */
|
|
9
|
+
readonly errorBoundary?: unknown;
|
|
10
|
+
}
|
|
11
|
+
type ReactCapture = (thrown: unknown, errorInfo: ReactErrorInfo, handled?: boolean) => void;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/error-boundary.types.d.ts
|
|
14
|
+
interface ForgeErrorBoundaryProps {
|
|
15
|
+
readonly children?: ReactNode;
|
|
16
|
+
/** Rendered instead of the children once they threw, nothing when absent. */
|
|
17
|
+
readonly fallback?: ReactNode | ((error: Error) => ReactNode);
|
|
18
|
+
/** Called after the error is tracked. */
|
|
19
|
+
readonly onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
20
|
+
}
|
|
21
|
+
interface ForgeErrorBoundaryState {
|
|
22
|
+
readonly error?: Error;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/error-handler.types.d.ts
|
|
26
|
+
type ForgeErrorHandler = (error: unknown, errorInfo: ReactErrorInfo) => void;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/plugin.types.d.ts
|
|
29
|
+
interface ReactPlugin extends StatsPlugin {
|
|
30
|
+
readonly ErrorBoundary: ComponentClass<ForgeErrorBoundaryProps, ForgeErrorBoundaryState>;
|
|
31
|
+
errorHandler(callback?: ForgeErrorHandler): ForgeErrorHandler;
|
|
32
|
+
readonly captureError: ReactCapture;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/plugin.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Returns the React plugin for `init`; its tools track errors through the client `init` created.
|
|
38
|
+
* @returns the plugin
|
|
39
|
+
*/
|
|
40
|
+
declare function reactPlugin(): ReactPlugin;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { type ForgeErrorBoundaryProps, type ForgeErrorBoundaryState, type ForgeErrorHandler, type ReactCapture, type ReactErrorInfo, type ReactPlugin, reactPlugin };
|
|
43
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Component } from "react";
|
|
2
|
+
export * from "@codeserk/forge-stats";
|
|
3
|
+
//#region src/capture.const.ts
|
|
4
|
+
const REACT_FRAMEWORK = "react";
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/capture.ts
|
|
7
|
+
/**
|
|
8
|
+
* Returns a capture that tracks the error with its component stack, or only warns before the plugin is set up.
|
|
9
|
+
* @param getClient returns undefined until the plugin is set up
|
|
10
|
+
* @returns the capture
|
|
11
|
+
*/
|
|
12
|
+
function createReactCapture(getClient) {
|
|
13
|
+
return (thrown, errorInfo, handled = true) => {
|
|
14
|
+
const client = getClient();
|
|
15
|
+
if (!client) {
|
|
16
|
+
console.warn("[forge-stats] The react plugin is not set up, add it to the plugins of init");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
20
|
+
client.trackError(error, {
|
|
21
|
+
handled,
|
|
22
|
+
componentStack: errorInfo.componentStack ?? void 0,
|
|
23
|
+
framework: REACT_FRAMEWORK
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/error-boundary.ts
|
|
29
|
+
/**
|
|
30
|
+
* Returns an error boundary that tracks the errors of its children and renders the fallback.
|
|
31
|
+
* @param capture
|
|
32
|
+
* @returns the error boundary component
|
|
33
|
+
*/
|
|
34
|
+
function createErrorBoundary(capture) {
|
|
35
|
+
return class ForgeErrorBoundary extends Component {
|
|
36
|
+
constructor(..._args) {
|
|
37
|
+
super(..._args);
|
|
38
|
+
this.state = {};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns the state with what the children threw, as an error.
|
|
42
|
+
* @param thrown
|
|
43
|
+
* @returns the state
|
|
44
|
+
*/
|
|
45
|
+
static getDerivedStateFromError(thrown) {
|
|
46
|
+
return { error: thrown instanceof Error ? thrown : new Error(String(thrown)) };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Tracks the error as handled, then calls `onError`.
|
|
50
|
+
* @param error
|
|
51
|
+
* @param errorInfo
|
|
52
|
+
*/
|
|
53
|
+
componentDidCatch(error, errorInfo) {
|
|
54
|
+
capture(error, errorInfo);
|
|
55
|
+
this.props.onError?.(error, errorInfo);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns the children, or the fallback once they threw.
|
|
59
|
+
* @returns the children or the fallback
|
|
60
|
+
*/
|
|
61
|
+
render() {
|
|
62
|
+
const { error } = this.state;
|
|
63
|
+
if (!error) return this.props.children ?? null;
|
|
64
|
+
const { fallback } = this.props;
|
|
65
|
+
return typeof fallback === "function" ? fallback(error) : fallback ?? null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/error-handler.ts
|
|
71
|
+
/**
|
|
72
|
+
* Returns a `createRoot` error handler that tracks the error, logs it as React does, then calls the callback.
|
|
73
|
+
* @param capture
|
|
74
|
+
* @param callback
|
|
75
|
+
* @returns the root error handler
|
|
76
|
+
*/
|
|
77
|
+
function createErrorHandler(capture, callback) {
|
|
78
|
+
return (error, errorInfo) => {
|
|
79
|
+
capture(error, errorInfo, errorInfo.errorBoundary !== void 0);
|
|
80
|
+
console.error(error);
|
|
81
|
+
callback?.(error, errorInfo);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/plugin.const.ts
|
|
86
|
+
const REACT_PLUGIN_NAME = "react";
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/plugin.ts
|
|
89
|
+
/**
|
|
90
|
+
* Returns the React plugin for `init`; its tools track errors through the client `init` created.
|
|
91
|
+
* @returns the plugin
|
|
92
|
+
*/
|
|
93
|
+
function reactPlugin() {
|
|
94
|
+
let client;
|
|
95
|
+
const captureError = createReactCapture(() => client);
|
|
96
|
+
return {
|
|
97
|
+
name: REACT_PLUGIN_NAME,
|
|
98
|
+
setup: (pluginClient) => {
|
|
99
|
+
client = pluginClient;
|
|
100
|
+
},
|
|
101
|
+
ErrorBoundary: createErrorBoundary(captureError),
|
|
102
|
+
errorHandler: (callback) => createErrorHandler(captureError, callback),
|
|
103
|
+
captureError
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { reactPlugin };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/capture.const.ts","../src/capture.ts","../src/error-boundary.ts","../src/error-handler.ts","../src/plugin.const.ts","../src/plugin.ts"],"sourcesContent":["export const REACT_FRAMEWORK = 'react'\n","import type { Client } from '@codeserk/forge-stats'\n\nimport { REACT_FRAMEWORK } from './capture.const'\nimport type { ReactCapture } from './capture.types'\n\n/**\n * Returns a capture that tracks the error with its component stack, or only warns before the plugin is set up.\n * @param getClient returns undefined until the plugin is set up\n * @returns the capture\n */\nexport function createReactCapture(getClient: () => Client | undefined): ReactCapture {\n return (thrown, errorInfo, handled = true) => {\n const client = getClient()\n if (!client) {\n console.warn('[forge-stats] The react plugin is not set up, add it to the plugins of init')\n return\n }\n\n const error = thrown instanceof Error ? thrown : new Error(String(thrown))\n client.trackError(error, {\n handled,\n componentStack: errorInfo.componentStack ?? undefined,\n framework: REACT_FRAMEWORK,\n })\n }\n}\n","import { Component, type ComponentClass, type ErrorInfo, type ReactNode } from 'react'\n\nimport type { ReactCapture } from './capture.types'\nimport type { ForgeErrorBoundaryProps, ForgeErrorBoundaryState } from './error-boundary.types'\n\n/**\n * Returns an error boundary that tracks the errors of its children and renders the fallback.\n * @param capture\n * @returns the error boundary component\n */\nexport function createErrorBoundary(\n capture: ReactCapture,\n): ComponentClass<ForgeErrorBoundaryProps, ForgeErrorBoundaryState> {\n return class ForgeErrorBoundary extends Component<\n ForgeErrorBoundaryProps,\n ForgeErrorBoundaryState\n > {\n override state: ForgeErrorBoundaryState = {}\n\n /**\n * Returns the state with what the children threw, as an error.\n * @param thrown\n * @returns the state\n */\n static getDerivedStateFromError(thrown: unknown): ForgeErrorBoundaryState {\n return { error: thrown instanceof Error ? thrown : new Error(String(thrown)) }\n }\n\n /**\n * Tracks the error as handled, then calls `onError`.\n * @param error\n * @param errorInfo\n */\n override componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n capture(error, errorInfo)\n this.props.onError?.(error, errorInfo)\n }\n\n /**\n * Returns the children, or the fallback once they threw.\n * @returns the children or the fallback\n */\n override render(): ReactNode {\n const { error } = this.state\n if (!error) {\n return this.props.children ?? null\n }\n\n const { fallback } = this.props\n\n return typeof fallback === 'function' ? fallback(error) : (fallback ?? null)\n }\n }\n}\n","import type { ReactCapture } from './capture.types'\nimport type { ForgeErrorHandler } from './error-handler.types'\n\n/**\n * Returns a `createRoot` error handler that tracks the error, logs it as React does, then calls the callback.\n * @param capture\n * @param callback\n * @returns the root error handler\n */\nexport function createErrorHandler(\n capture: ReactCapture,\n callback?: ForgeErrorHandler,\n): ForgeErrorHandler {\n return (error, errorInfo) => {\n capture(error, errorInfo, errorInfo.errorBoundary !== undefined)\n console.error(error)\n callback?.(error, errorInfo)\n }\n}\n","export const REACT_PLUGIN_NAME = 'react'\n","import type { Client } from '@codeserk/forge-stats'\n\nimport { createReactCapture } from './capture'\nimport { createErrorBoundary } from './error-boundary'\nimport { createErrorHandler } from './error-handler'\nimport { REACT_PLUGIN_NAME } from './plugin.const'\nimport type { ReactPlugin } from './plugin.types'\n\n/**\n * Returns the React plugin for `init`; its tools track errors through the client `init` created.\n * @returns the plugin\n */\nexport function reactPlugin(): ReactPlugin {\n let client: Client | undefined\n const captureError = createReactCapture(() => client)\n\n return {\n name: REACT_PLUGIN_NAME,\n setup: (pluginClient) => {\n client = pluginClient\n },\n ErrorBoundary: createErrorBoundary(captureError),\n errorHandler: (callback) => createErrorHandler(captureError, callback),\n captureError,\n }\n}\n"],"mappings":";;;AAAA,MAAa,kBAAkB;;;;;;;;ACU/B,SAAgB,mBAAmB,WAAmD;AACpF,SAAQ,QAAQ,WAAW,UAAU,SAAS;EAC5C,MAAM,SAAS,WAAW;AAC1B,MAAI,CAAC,QAAQ;AACX,WAAQ,KAAK,8EAA8E;AAC3F;;EAGF,MAAM,QAAQ,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,OAAO,CAAC;AAC1E,SAAO,WAAW,OAAO;GACvB;GACA,gBAAgB,UAAU,kBAAkB,KAAA;GAC5C,WAAW;GACZ,CAAC;;;;;;;;;;ACbN,SAAgB,oBACd,SACkE;AAClE,QAAO,MAAM,2BAA2B,UAGtC;;;gBAC0C,EAAE;;;;;;;EAO5C,OAAO,yBAAyB,QAA0C;AACxE,UAAO,EAAE,OAAO,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,OAAO,CAAC,EAAE;;;;;;;EAQhF,kBAA2B,OAAc,WAA4B;AACnE,WAAQ,OAAO,UAAU;AACzB,QAAK,MAAM,UAAU,OAAO,UAAU;;;;;;EAOxC,SAA6B;GAC3B,MAAM,EAAE,UAAU,KAAK;AACvB,OAAI,CAAC,MACH,QAAO,KAAK,MAAM,YAAY;GAGhC,MAAM,EAAE,aAAa,KAAK;AAE1B,UAAO,OAAO,aAAa,aAAa,SAAS,MAAM,GAAI,YAAY;;;;;;;;;;;;ACzC7E,SAAgB,mBACd,SACA,UACmB;AACnB,SAAQ,OAAO,cAAc;AAC3B,UAAQ,OAAO,WAAW,UAAU,kBAAkB,KAAA,EAAU;AAChE,UAAQ,MAAM,MAAM;AACpB,aAAW,OAAO,UAAU;;;;;AChBhC,MAAa,oBAAoB;;;;;;;ACYjC,SAAgB,cAA2B;CACzC,IAAI;CACJ,MAAM,eAAe,yBAAyB,OAAO;AAErD,QAAO;EACL,MAAM;EACN,QAAQ,iBAAiB;AACvB,YAAS;;EAEX,eAAe,oBAAoB,aAAa;EAChD,eAAe,aAAa,mBAAmB,cAAc,SAAS;EACtE;EACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeserk/forge-stats-react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "React plugin for Forge Stats. Error boundary and root error handlers that track errors with their component stack.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"forge",
|
|
8
|
+
"analytics",
|
|
9
|
+
"stats",
|
|
10
|
+
"error-tracking",
|
|
11
|
+
"react"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.mjs",
|
|
18
|
+
"types": "./dist/index.d.cts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.mts",
|
|
23
|
+
"default": "./dist/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
27
|
+
"default": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"dev": "tsdown --watch",
|
|
39
|
+
"test": "jest",
|
|
40
|
+
"lint": "eslint src",
|
|
41
|
+
"version:patch": "npm version patch",
|
|
42
|
+
"version:minor": "npm version minor",
|
|
43
|
+
"version:major": "npm version major",
|
|
44
|
+
"publish:dry": "npm publish --dry-run",
|
|
45
|
+
"publish:npm": "npm run build && npm publish"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@codeserk/forge-stats": "^0.4.0",
|
|
49
|
+
"react": ">=16.8"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/jest": "^30.0.0",
|
|
53
|
+
"@types/react": "^19.0.0",
|
|
54
|
+
"@types/react-dom": "^19.0.0",
|
|
55
|
+
"jest": "^30.3.0",
|
|
56
|
+
"jest-environment-jsdom": "^30.3.0",
|
|
57
|
+
"react": "^19.2.0",
|
|
58
|
+
"react-dom": "^19.2.0",
|
|
59
|
+
"ts-jest": "^29.4.6",
|
|
60
|
+
"tsdown": "^0.21.4",
|
|
61
|
+
"typescript": "^5.7.0"
|
|
62
|
+
}
|
|
63
|
+
}
|