@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 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
- # Temporary Holding Version
1
+ # @codeserk/forge-stats-react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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"}
@@ -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
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }