gracefulerrors 0.1.0 → 0.1.1

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 CHANGED
@@ -17,12 +17,18 @@ gracefulerrors is a TypeScript library for turning technical errors into consist
17
17
  npm install gracefulerrors
18
18
  ```
19
19
 
20
- If you use the React SDK or the Sonner adapter, install the matching peer dependencies in your app:
20
+ If you use the React SDK or the Sonner adapter, install the matching peer dependencies:
21
21
 
22
22
  ```bash
23
23
  npm install react react-dom sonner
24
24
  ```
25
25
 
26
+ If you use the Vue SDK:
27
+
28
+ ```bash
29
+ npm install vue
30
+ ```
31
+
26
32
  ## Quick Start
27
33
 
28
34
  ```ts
@@ -120,6 +126,44 @@ export function App() {
120
126
  - `useFieldError(field)` for inline error state
121
127
  - `ErrorBoundaryWithEngine` for catching runtime errors and forwarding them to the engine
122
128
 
129
+ ## Vue Integration
130
+
131
+ ```ts
132
+ import { createErrorEngine } from 'gracefulerrors'
133
+ import { createErrorEnginePlugin, useErrorEngine } from 'gracefulerrors/vue'
134
+ import { createApp } from 'vue'
135
+ import App from './App.vue'
136
+
137
+ const engine = createErrorEngine({
138
+ registry: {
139
+ NETWORK_ERROR: { ui: 'toast', message: 'Network error' },
140
+ },
141
+ })
142
+
143
+ const app = createApp(App)
144
+ app.use(createErrorEnginePlugin(engine))
145
+ app.mount('#app')
146
+ ```
147
+
148
+ Inside any component:
149
+
150
+ ```ts
151
+ import { useErrorEngine, useFieldError } from 'gracefulerrors/vue'
152
+
153
+ // Access the engine
154
+ const engine = useErrorEngine()
155
+ engine?.handle({ code: 'NETWORK_ERROR' })
156
+
157
+ // Bind inline field errors
158
+ const { error } = useFieldError('email')
159
+ // error is a Ref<AppError | null> that updates reactively
160
+ ```
161
+
162
+ `gracefulerrors/vue` also exports:
163
+
164
+ - `provideErrorEngine(engine)` for local subtree injection (alternative to the global plugin)
165
+ - `ErrorBoundaryWithEngine` component for catching runtime errors and forwarding them to the engine
166
+
123
167
  ## Sonner Adapter
124
168
 
125
169
  ```tsx
@@ -161,6 +205,7 @@ Type exports are available from the root package as well.
161
205
  Additional entry points:
162
206
 
163
207
  - `gracefulerrors/react`
208
+ - `gracefulerrors/vue`
164
209
  - `gracefulerrors/sonner`
165
210
  - `gracefulerrors/internal` for internal testing and low-level integration only
166
211
 
@@ -183,7 +228,7 @@ Common engine options include:
183
228
  ## Package Notes
184
229
 
185
230
  - Package format: ESM and CJS via conditional exports
186
- - Runtime peers: `react`, `react-dom`, and `sonner` are optional peer dependencies
231
+ - Runtime peers: `react`, `react-dom`, `sonner`, and `vue` are optional peer dependencies
187
232
  - Current version: `0.1.0`
188
233
 
189
234
  ## Development
package/dist/vue.cjs ADDED
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
4
+
5
+ // src/vue.ts
6
+ var ErrorEngineKey = /* @__PURE__ */ Symbol("ErrorEngine");
7
+ function createErrorEnginePlugin(engine) {
8
+ return {
9
+ install(app) {
10
+ app.provide(ErrorEngineKey, engine);
11
+ }
12
+ };
13
+ }
14
+ function provideErrorEngine(engine) {
15
+ vue.provide(ErrorEngineKey, engine);
16
+ }
17
+ function useErrorEngine() {
18
+ const engine = vue.inject(ErrorEngineKey, null);
19
+ if (!engine && process.env["NODE_ENV"] === "development") {
20
+ console.error("[gracefulerrors] useErrorEngine called outside of a provider (ErrorEngineKey not found).");
21
+ }
22
+ return engine;
23
+ }
24
+ function useFieldError(field) {
25
+ const engine = useErrorEngine();
26
+ const error = vue.ref(null);
27
+ let unsubscribe = null;
28
+ vue.onMounted(() => {
29
+ if (!engine) return;
30
+ unsubscribe = engine.subscribe((event) => {
31
+ if (event.type === "ERROR_ADDED" && event.action === "inline") {
32
+ if (event.error.context?.field === field) {
33
+ error.value = event.error;
34
+ }
35
+ } else if (event.type === "ERROR_CLEARED") {
36
+ if (error.value?.code === event.code) {
37
+ error.value = null;
38
+ }
39
+ } else if (event.type === "ALL_CLEARED") {
40
+ error.value = null;
41
+ }
42
+ });
43
+ });
44
+ vue.onUnmounted(() => {
45
+ unsubscribe?.();
46
+ });
47
+ return { error };
48
+ }
49
+ var ErrorBoundaryWithEngine = vue.defineComponent({
50
+ name: "ErrorBoundaryWithEngine",
51
+ props: {
52
+ fallback: {
53
+ type: [String, Object],
54
+ default: null
55
+ }
56
+ },
57
+ setup(props, { slots }) {
58
+ const engine = useErrorEngine();
59
+ const hasError = vue.ref(false);
60
+ vue.onErrorCaptured((err) => {
61
+ engine?.handle(err);
62
+ hasError.value = true;
63
+ return false;
64
+ });
65
+ return () => {
66
+ if (hasError.value) {
67
+ if (slots.fallback) return slots.fallback();
68
+ if (props.fallback) {
69
+ return typeof props.fallback === "string" ? vue.h("span", props.fallback) : props.fallback;
70
+ }
71
+ return null;
72
+ }
73
+ return slots.default?.();
74
+ };
75
+ }
76
+ });
77
+
78
+ exports.ErrorBoundaryWithEngine = ErrorBoundaryWithEngine;
79
+ exports.ErrorEngineKey = ErrorEngineKey;
80
+ exports.createErrorEnginePlugin = createErrorEnginePlugin;
81
+ exports.provideErrorEngine = provideErrorEngine;
82
+ exports.useErrorEngine = useErrorEngine;
83
+ exports.useFieldError = useFieldError;
84
+ //# sourceMappingURL=vue.cjs.map
85
+ //# sourceMappingURL=vue.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vue.ts"],"names":["provide","inject","ref","onMounted","onUnmounted","defineComponent","onErrorCaptured","h"],"mappings":"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","file":"vue.cjs","sourcesContent":["import {\n inject,\n provide,\n ref,\n onMounted,\n onUnmounted,\n defineComponent,\n h,\n onErrorCaptured,\n} from 'vue'\nimport type { InjectionKey, Plugin, Ref, VNode } from 'vue'\nimport type { ErrorEngine, AppError, StateListener } from './types'\n\nexport const ErrorEngineKey: InjectionKey<ErrorEngine> = Symbol('ErrorEngine')\n\nexport function createErrorEnginePlugin<TCode extends string = string>(\n engine: ErrorEngine<TCode>\n): Plugin {\n return {\n install(app) {\n app.provide(ErrorEngineKey, engine)\n },\n }\n}\n\nexport function provideErrorEngine<TCode extends string = string>(\n engine: ErrorEngine<TCode>\n): void {\n provide(ErrorEngineKey, engine)\n}\n\nexport function useErrorEngine<TCode extends string = string>(): ErrorEngine<TCode> | null {\n const engine = inject(ErrorEngineKey, null)\n if (!engine && process.env['NODE_ENV'] === 'development') {\n console.error('[gracefulerrors] useErrorEngine called outside of a provider (ErrorEngineKey not found).')\n }\n return engine as ErrorEngine<TCode> | null\n}\n\nexport function useFieldError<TField extends string = string>(\n field: TField\n): { error: Ref<AppError<string, TField> | null> } {\n const engine = useErrorEngine()\n const error = ref<AppError<string, TField> | null>(null) as Ref<AppError<string, TField> | null>\n let unsubscribe: (() => void) | null = null\n\n onMounted(() => {\n if (!engine) return\n unsubscribe = engine.subscribe((event: Parameters<StateListener>[0]) => {\n if (event.type === 'ERROR_ADDED' && event.action === 'inline') {\n if (event.error.context?.field === field) {\n error.value = event.error as AppError<string, TField>\n }\n } else if (event.type === 'ERROR_CLEARED') {\n if (error.value?.code === event.code) {\n error.value = null\n }\n } else if (event.type === 'ALL_CLEARED') {\n error.value = null\n }\n })\n })\n\n onUnmounted(() => {\n unsubscribe?.()\n })\n\n return { error }\n}\n\nexport const ErrorBoundaryWithEngine = defineComponent({\n name: 'ErrorBoundaryWithEngine',\n props: {\n fallback: {\n type: [String, Object] as unknown as () => string | VNode,\n default: null,\n },\n },\n setup(props, { slots }) {\n const engine = useErrorEngine()\n const hasError = ref(false)\n\n onErrorCaptured((err: unknown) => {\n engine?.handle(err)\n hasError.value = true\n return false\n })\n\n return () => {\n if (hasError.value) {\n if (slots.fallback) return slots.fallback()\n if (props.fallback) {\n return typeof props.fallback === 'string'\n ? h('span', props.fallback)\n : (props.fallback as VNode)\n }\n return null\n }\n return slots.default?.()\n }\n },\n})\n"]}
package/dist/vue.d.cts ADDED
@@ -0,0 +1,32 @@
1
+ import * as vue from 'vue';
2
+ import { VNode, InjectionKey, Plugin, Ref } from 'vue';
3
+ import { a as ErrorEngine, A as AppError } from './types-CsPmpcbL.cjs';
4
+
5
+ declare const ErrorEngineKey: InjectionKey<ErrorEngine>;
6
+ declare function createErrorEnginePlugin<TCode extends string = string>(engine: ErrorEngine<TCode>): Plugin;
7
+ declare function provideErrorEngine<TCode extends string = string>(engine: ErrorEngine<TCode>): void;
8
+ declare function useErrorEngine<TCode extends string = string>(): ErrorEngine<TCode> | null;
9
+ declare function useFieldError<TField extends string = string>(field: TField): {
10
+ error: Ref<AppError<string, TField> | null>;
11
+ };
12
+ declare const ErrorBoundaryWithEngine: vue.DefineComponent<vue.ExtractPropTypes<{
13
+ fallback: {
14
+ type: () => string | VNode;
15
+ default: null;
16
+ };
17
+ }>, () => VNode<vue.RendererNode, vue.RendererElement, {
18
+ [key: string]: any;
19
+ }> | VNode<vue.RendererNode, vue.RendererElement, {
20
+ [key: string]: any;
21
+ }>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
22
+ fallback: {
23
+ type: () => string | VNode;
24
+ default: null;
25
+ };
26
+ }>> & Readonly<{}>, {
27
+ fallback: string | VNode<vue.RendererNode, vue.RendererElement, {
28
+ [key: string]: any;
29
+ }>;
30
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
31
+
32
+ export { ErrorBoundaryWithEngine, ErrorEngineKey, createErrorEnginePlugin, provideErrorEngine, useErrorEngine, useFieldError };
package/dist/vue.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import * as vue from 'vue';
2
+ import { VNode, InjectionKey, Plugin, Ref } from 'vue';
3
+ import { a as ErrorEngine, A as AppError } from './types-CsPmpcbL.js';
4
+
5
+ declare const ErrorEngineKey: InjectionKey<ErrorEngine>;
6
+ declare function createErrorEnginePlugin<TCode extends string = string>(engine: ErrorEngine<TCode>): Plugin;
7
+ declare function provideErrorEngine<TCode extends string = string>(engine: ErrorEngine<TCode>): void;
8
+ declare function useErrorEngine<TCode extends string = string>(): ErrorEngine<TCode> | null;
9
+ declare function useFieldError<TField extends string = string>(field: TField): {
10
+ error: Ref<AppError<string, TField> | null>;
11
+ };
12
+ declare const ErrorBoundaryWithEngine: vue.DefineComponent<vue.ExtractPropTypes<{
13
+ fallback: {
14
+ type: () => string | VNode;
15
+ default: null;
16
+ };
17
+ }>, () => VNode<vue.RendererNode, vue.RendererElement, {
18
+ [key: string]: any;
19
+ }> | VNode<vue.RendererNode, vue.RendererElement, {
20
+ [key: string]: any;
21
+ }>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
22
+ fallback: {
23
+ type: () => string | VNode;
24
+ default: null;
25
+ };
26
+ }>> & Readonly<{}>, {
27
+ fallback: string | VNode<vue.RendererNode, vue.RendererElement, {
28
+ [key: string]: any;
29
+ }>;
30
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
31
+
32
+ export { ErrorBoundaryWithEngine, ErrorEngineKey, createErrorEnginePlugin, provideErrorEngine, useErrorEngine, useFieldError };
package/dist/vue.js ADDED
@@ -0,0 +1,78 @@
1
+ import { defineComponent, ref, onErrorCaptured, h, inject, provide, onMounted, onUnmounted } from 'vue';
2
+
3
+ // src/vue.ts
4
+ var ErrorEngineKey = /* @__PURE__ */ Symbol("ErrorEngine");
5
+ function createErrorEnginePlugin(engine) {
6
+ return {
7
+ install(app) {
8
+ app.provide(ErrorEngineKey, engine);
9
+ }
10
+ };
11
+ }
12
+ function provideErrorEngine(engine) {
13
+ provide(ErrorEngineKey, engine);
14
+ }
15
+ function useErrorEngine() {
16
+ const engine = inject(ErrorEngineKey, null);
17
+ if (!engine && process.env["NODE_ENV"] === "development") {
18
+ console.error("[gracefulerrors] useErrorEngine called outside of a provider (ErrorEngineKey not found).");
19
+ }
20
+ return engine;
21
+ }
22
+ function useFieldError(field) {
23
+ const engine = useErrorEngine();
24
+ const error = ref(null);
25
+ let unsubscribe = null;
26
+ onMounted(() => {
27
+ if (!engine) return;
28
+ unsubscribe = engine.subscribe((event) => {
29
+ if (event.type === "ERROR_ADDED" && event.action === "inline") {
30
+ if (event.error.context?.field === field) {
31
+ error.value = event.error;
32
+ }
33
+ } else if (event.type === "ERROR_CLEARED") {
34
+ if (error.value?.code === event.code) {
35
+ error.value = null;
36
+ }
37
+ } else if (event.type === "ALL_CLEARED") {
38
+ error.value = null;
39
+ }
40
+ });
41
+ });
42
+ onUnmounted(() => {
43
+ unsubscribe?.();
44
+ });
45
+ return { error };
46
+ }
47
+ var ErrorBoundaryWithEngine = defineComponent({
48
+ name: "ErrorBoundaryWithEngine",
49
+ props: {
50
+ fallback: {
51
+ type: [String, Object],
52
+ default: null
53
+ }
54
+ },
55
+ setup(props, { slots }) {
56
+ const engine = useErrorEngine();
57
+ const hasError = ref(false);
58
+ onErrorCaptured((err) => {
59
+ engine?.handle(err);
60
+ hasError.value = true;
61
+ return false;
62
+ });
63
+ return () => {
64
+ if (hasError.value) {
65
+ if (slots.fallback) return slots.fallback();
66
+ if (props.fallback) {
67
+ return typeof props.fallback === "string" ? h("span", props.fallback) : props.fallback;
68
+ }
69
+ return null;
70
+ }
71
+ return slots.default?.();
72
+ };
73
+ }
74
+ });
75
+
76
+ export { ErrorBoundaryWithEngine, ErrorEngineKey, createErrorEnginePlugin, provideErrorEngine, useErrorEngine, useFieldError };
77
+ //# sourceMappingURL=vue.js.map
78
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vue.ts"],"names":[],"mappings":"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","file":"vue.js","sourcesContent":["import {\n inject,\n provide,\n ref,\n onMounted,\n onUnmounted,\n defineComponent,\n h,\n onErrorCaptured,\n} from 'vue'\nimport type { InjectionKey, Plugin, Ref, VNode } from 'vue'\nimport type { ErrorEngine, AppError, StateListener } from './types'\n\nexport const ErrorEngineKey: InjectionKey<ErrorEngine> = Symbol('ErrorEngine')\n\nexport function createErrorEnginePlugin<TCode extends string = string>(\n engine: ErrorEngine<TCode>\n): Plugin {\n return {\n install(app) {\n app.provide(ErrorEngineKey, engine)\n },\n }\n}\n\nexport function provideErrorEngine<TCode extends string = string>(\n engine: ErrorEngine<TCode>\n): void {\n provide(ErrorEngineKey, engine)\n}\n\nexport function useErrorEngine<TCode extends string = string>(): ErrorEngine<TCode> | null {\n const engine = inject(ErrorEngineKey, null)\n if (!engine && process.env['NODE_ENV'] === 'development') {\n console.error('[gracefulerrors] useErrorEngine called outside of a provider (ErrorEngineKey not found).')\n }\n return engine as ErrorEngine<TCode> | null\n}\n\nexport function useFieldError<TField extends string = string>(\n field: TField\n): { error: Ref<AppError<string, TField> | null> } {\n const engine = useErrorEngine()\n const error = ref<AppError<string, TField> | null>(null) as Ref<AppError<string, TField> | null>\n let unsubscribe: (() => void) | null = null\n\n onMounted(() => {\n if (!engine) return\n unsubscribe = engine.subscribe((event: Parameters<StateListener>[0]) => {\n if (event.type === 'ERROR_ADDED' && event.action === 'inline') {\n if (event.error.context?.field === field) {\n error.value = event.error as AppError<string, TField>\n }\n } else if (event.type === 'ERROR_CLEARED') {\n if (error.value?.code === event.code) {\n error.value = null\n }\n } else if (event.type === 'ALL_CLEARED') {\n error.value = null\n }\n })\n })\n\n onUnmounted(() => {\n unsubscribe?.()\n })\n\n return { error }\n}\n\nexport const ErrorBoundaryWithEngine = defineComponent({\n name: 'ErrorBoundaryWithEngine',\n props: {\n fallback: {\n type: [String, Object] as unknown as () => string | VNode,\n default: null,\n },\n },\n setup(props, { slots }) {\n const engine = useErrorEngine()\n const hasError = ref(false)\n\n onErrorCaptured((err: unknown) => {\n engine?.handle(err)\n hasError.value = true\n return false\n })\n\n return () => {\n if (hasError.value) {\n if (slots.fallback) return slots.fallback()\n if (props.fallback) {\n return typeof props.fallback === 'string'\n ? h('span', props.fallback)\n : (props.fallback as VNode)\n }\n return null\n }\n return slots.default?.()\n }\n },\n})\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gracefulerrors",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Transform technical errors into consistent, user-friendly experiences",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,6 +24,11 @@
24
24
  "types": "./dist/internal.d.ts",
25
25
  "import": "./dist/internal.mjs",
26
26
  "require": "./dist/internal.cjs"
27
+ },
28
+ "./vue": {
29
+ "types": "./dist/vue.d.ts",
30
+ "import": "./dist/vue.mjs",
31
+ "require": "./dist/vue.cjs"
27
32
  }
28
33
  },
29
34
  "main": "./dist/index.cjs",
@@ -45,7 +50,8 @@
45
50
  "peerDependencies": {
46
51
  "react": ">=18",
47
52
  "react-dom": ">=18",
48
- "sonner": ">=1"
53
+ "sonner": ">=1",
54
+ "vue": ">=3"
49
55
  },
50
56
  "peerDependenciesMeta": {
51
57
  "sonner": {
@@ -56,6 +62,9 @@
56
62
  },
57
63
  "react-dom": {
58
64
  "optional": true
65
+ },
66
+ "vue": {
67
+ "optional": true
59
68
  }
60
69
  },
61
70
  "tsd": {
@@ -68,6 +77,7 @@
68
77
  "@types/react": "^18.0.0",
69
78
  "@types/react-dom": "^18.0.0",
70
79
  "@vitest/coverage-v8": "^1.0.0",
80
+ "@vue/test-utils": "^2.4.6",
71
81
  "jsdom": "^24.0.0",
72
82
  "react": "^18.0.0",
73
83
  "react-dom": "^18.0.0",
@@ -75,6 +85,7 @@
75
85
  "tsd": "^0.31.0",
76
86
  "tsup": "^8.0.0",
77
87
  "typescript": "^5.0.0",
78
- "vitest": "^1.0.0"
88
+ "vitest": "^1.0.0",
89
+ "vue": "^3.5.32"
79
90
  }
80
91
  }