@ovineko/spa-guard 0.0.1-alpha-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.
Files changed (39) hide show
  1. package/README.md +695 -0
  2. package/dist/chunk-BL4EG6R7.js +14 -0
  3. package/dist/chunk-MLKGABMK.js +9 -0
  4. package/dist/chunk-VWV6L4Q2.js +51 -0
  5. package/dist/chunk-W65YKSMF.js +6 -0
  6. package/dist/chunk-XV2YCVOR.js +12 -0
  7. package/dist/common/constants.d.ts +5 -0
  8. package/dist/common/events/index.d.ts +2 -0
  9. package/dist/common/events/internal.d.ts +4 -0
  10. package/dist/common/events/types.d.ts +8 -0
  11. package/dist/common/fallbackHtml.generated.d.ts +1 -0
  12. package/dist/common/index.d.ts +3 -0
  13. package/dist/common/index.js +358 -0
  14. package/dist/common/isChunkError.d.ts +1 -0
  15. package/dist/common/listen/index.d.ts +1 -0
  16. package/dist/common/listen/internal.d.ts +1 -0
  17. package/dist/common/log.d.ts +1 -0
  18. package/dist/common/options.d.ts +12 -0
  19. package/dist/common/reload.d.ts +1 -0
  20. package/dist/common/sendBeacon.d.ts +2 -0
  21. package/dist/common/serializeError.d.ts +1 -0
  22. package/dist/fastify/index.d.ts +45 -0
  23. package/dist/fastify/index.js +66 -0
  24. package/dist/inline/index.d.ts +1 -0
  25. package/dist/react/index.d.ts +1 -0
  26. package/dist/react/index.js +0 -0
  27. package/dist/react-error-boundary/index.d.ts +1 -0
  28. package/dist/react-error-boundary/index.js +7 -0
  29. package/dist/react-router/index.d.ts +1 -0
  30. package/dist/react-router/index.js +12 -0
  31. package/dist/schema/index.d.ts +8 -0
  32. package/dist/schema/index.js +7 -0
  33. package/dist/schema/parse.d.ts +6 -0
  34. package/dist/schema/parse.js +8 -0
  35. package/dist/vite-plugin/index.d.ts +6 -0
  36. package/dist/vite-plugin/index.js +38 -0
  37. package/dist-inline/index.js +1 -0
  38. package/dist-inline-trace/index.js +360 -0
  39. package/package.json +86 -0
@@ -0,0 +1,6 @@
1
+ export declare const parseBeacon: (value: unknown) => {
2
+ errorMessage?: string | undefined;
3
+ eventMessage?: string | undefined;
4
+ eventName?: string | undefined;
5
+ serialized?: string | undefined;
6
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ parseBeacon
3
+ } from "../chunk-BL4EG6R7.js";
4
+ import "../chunk-XV2YCVOR.js";
5
+ import "../chunk-MLKGABMK.js";
6
+ export {
7
+ parseBeacon
8
+ };
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from "vite";
2
+ import { type Options } from "../common/options";
3
+ export interface VitePluginOptions extends Options {
4
+ trace?: boolean;
5
+ }
6
+ export declare const spaGuardVitePlugin: (options?: VitePluginOptions) => Plugin;
@@ -0,0 +1,38 @@
1
+ import {
2
+ name,
3
+ optionsWindowKey
4
+ } from "../chunk-VWV6L4Q2.js";
5
+ import "../chunk-MLKGABMK.js";
6
+
7
+ // src/vite-plugin/index.ts
8
+ import fsPromise from "fs/promises";
9
+ import path from "path";
10
+ var getInlineScript = async (options) => {
11
+ const buildDir = options.trace ? "dist-inline-trace" : "dist-inline";
12
+ const script = await fsPromise.readFile(path.join(import.meta.dirname, `../${buildDir}/index.js`), "utf8").then((r) => r.trim());
13
+ return `window.${optionsWindowKey}=${JSON.stringify(options)};${script}`;
14
+ };
15
+ var spaGuardVitePlugin = (options = {}) => {
16
+ return {
17
+ name: `${name}/vite-plugin`,
18
+ transformIndexHtml: {
19
+ handler: async (html) => {
20
+ const inlineScript = await getInlineScript(options);
21
+ return {
22
+ html,
23
+ tags: [
24
+ {
25
+ children: inlineScript,
26
+ injectTo: "head-prepend",
27
+ tag: "script"
28
+ }
29
+ ]
30
+ };
31
+ },
32
+ order: "post"
33
+ }
34
+ };
35
+ };
36
+ export {
37
+ spaGuardVitePlugin
38
+ };
@@ -0,0 +1 @@
1
+ var e="@ovineko/spa-guard",r=Symbol.for(e+":event-subscribers"),t=(Symbol.for(e+":internal-config"),"spaGuardRetryId"),o="spaGuardRetryAttempt";globalThis.window&&!globalThis.window[r]&&(globalThis.window[r]=new Set);var n=globalThis.window?.[r]??new Set,i=e=>{let r=a(e);return!!r&&[/Failed to fetch dynamically imported module/i,/Importing a module script failed/i,/error loading dynamically imported module/i,/Unable to preload CSS/i,/Loading chunk \d+ failed/i,/Loading CSS chunk \d+ failed/i,/ChunkLoadError/i,/Failed to fetch/i,/NaN/i].some(e=>e.test(r))},a=e=>e instanceof Error?e.message:"string"==typeof e?e:e&&"object"==typeof e&&"message"in e?e.message+"":e&&"object"==typeof e&&"reason"in e?a(e.reason):null,l=e=>"[spa-guard] "+e,s={fallbackHtml:'<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif"><div style="text-align:center"><h1>Something went wrong</h1><p>Please refresh the page to continue.</p><button onclick="location.reload()">Refresh Page</button></div></div>',reloadDelays:[1e3,2e3,5e3],useRetryId:!0},c=()=>{let e=globalThis.window?.__SPA_GUARD_OPTIONS__;return{...s,...e,reportBeacon:{...s.reportBeacon,...e?.reportBeacon}}},d=e=>{let r=c();if(!r.reportBeacon?.endpoint)return void console.warn(l("Report endpoint is not configured"));let t=JSON.stringify(e);"function"==typeof globalThis.window?.navigator.sendBeacon&&navigator.sendBeacon(r.reportBeacon.endpoint,t)||fetch(r.reportBeacon.endpoint,{body:t,keepalive:!0,method:"POST"}).catch(e=>{console.error(l("Failed to send beacon:"),e)})},u=e=>{let r=c(),n=r.reloadDelays??[1e3,2e3,5e3],i=r.useRetryId??!0,a=i?(()=>{try{let e=new URLSearchParams(window.location.search),r=e.get(t),n=e.get(o);return r&&n?{retryAttempt:parseInt(n,10),retryId:r}:null}catch{return null}})():null,s=a?a.retryAttempt:0;if(s>=n.length)return console.error(l("All reload attempts exhausted"),e),d({errorMessage:"Exceeded maximum reload attempts",eventName:"chunk_error_max_reloads",serialized:JSON.stringify({error:e+"",retryAttempt:s,retryId:a?.retryId})}),void m();let u=s+1,f=a?.retryId??(()=>{if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint32Array(2);return crypto.getRandomValues(e),`${Date.now()}-${e[0].toString(36)}${e[1].toString(36)}`}return`${Date.now()}-${Math.random().toString(36).slice(2,15)}`})(),p=n[s];console.warn(l(`Reload attempt ${u}/${n.length} in ${p}ms (retryId: ${f})`),e),setTimeout(()=>{if(i){let e=((e,r)=>{let n=new URL(window.location.href);return n.searchParams.set(t,e),n.searchParams.set(o,r+""),n.toString()})(f,u);window.location.href=e}else window.location.reload()},p)},m=()=>{let e=c().fallbackHtml;if(e)try{document.body.innerHTML=e}catch(e){console.error(l("Failed to inject fallback UI"),e)}else console.error(l("No fallback UI configured"))},f=e=>{try{let r=p(e);return JSON.stringify(r,null,2)}catch{return JSON.stringify({error:"Failed to serialize error",fallback:e+""})}},p=e=>{if(null==e)return{type:"null",value:e};if("object"!=typeof e)return{type:typeof e,value:e};if(e instanceof Error)return{message:e.message,name:e.name,stack:e.stack,type:"Error",...y(e)};if("reason"in e&&"promise"in e)return{reason:p(e.reason),type:"PromiseRejectionEvent"};if("error"in e&&"message"in e&&"filename"in e)return{colno:e.colno,error:p(e.error),filename:e.filename,lineno:e.lineno,message:e.message,type:"ErrorEvent"};if("violatedDirective"in e&&"blockedURI"in e){let r=e;return{blockedURI:r.blockedURI,columnNumber:r.columnNumber,effectiveDirective:r.effectiveDirective,lineNumber:r.lineNumber,originalPolicy:r.originalPolicy,sourceFile:r.sourceFile,type:"SecurityPolicyViolationEvent",violatedDirective:r.violatedDirective}}if("type"in e&&"target"in e){let r=e;return{eventType:r.type,target:g(r.target),timeStamp:r.timeStamp,type:"Event"}}return{type:"object",value:v(e)}},y=e=>{let r={};for(let t of Object.getOwnPropertyNames(e))if(!["message","name","stack"].includes(t))try{r[t]=e[t]}catch{}return r},g=e=>e?e instanceof HTMLElement?{className:e.className,href:e.href,id:e.id,src:e.src,tagName:e.tagName}:{type:e+""}:null,v=e=>{let r={};for(let t of Object.keys(e))try{let o=e[t];r[t]="object"==typeof o?o+"":o}catch{}return r};(()=>{(e=>{n.forEach(r=>r(e))})({name:"test"});let e=window.addEventListener;e("error",e=>{if(console.error(l("error:capture:"),e),i(e))return e.preventDefault(),void u(e);let r=f(e);d({errorMessage:e.message,eventName:"error",serialized:r})},!0),e("error",e=>{console.error(l("error:"),e)}),e("unhandledrejection",e=>{if(console.error(l("unhandledrejection:"),e),i(e.reason))return e.preventDefault(),void u(e.reason);let r=f(e);d({errorMessage:e.reason+"",eventName:"unhandledrejection",serialized:r})}),e("uncaughtException",e=>{console.error(l("uncaughtException:"),e);let r=f(e);d({eventName:"uncaughtException",serialized:r})}),e("securitypolicyviolation",e=>{console.error(l("CSP violation:"),e.blockedURI,e.violatedDirective);let r=f(e);d({eventMessage:`${e.violatedDirective}: ${e.blockedURI}`,eventName:"securitypolicyviolation",serialized:r})}),e("vite:preloadError",e=>{console.error(l("vite:preloadError:"),e),e.preventDefault(),u(e)})})();
@@ -0,0 +1,360 @@
1
+ // package.json
2
+ var name = "@ovineko/spa-guard";
3
+
4
+ // src/common/constants.ts
5
+ var optionsWindowKey = "__SPA_GUARD_OPTIONS__";
6
+ var eventSubscribersWindowKey = /* @__PURE__ */ Symbol.for(`${name}:event-subscribers`);
7
+ var internalConfigWindowKey = /* @__PURE__ */ Symbol.for(`${name}:internal-config`);
8
+ var RETRY_ID_PARAM = "spaGuardRetryId";
9
+ var RETRY_ATTEMPT_PARAM = "spaGuardRetryAttempt";
10
+
11
+ // src/common/events/internal.ts
12
+ if (globalThis.window && !globalThis.window[eventSubscribersWindowKey]) {
13
+ globalThis.window[eventSubscribersWindowKey] = /* @__PURE__ */ new Set();
14
+ }
15
+ var subscribers = globalThis.window?.[eventSubscribersWindowKey] ?? /* @__PURE__ */ new Set();
16
+ var emitEvent = (event) => {
17
+ subscribers.forEach((cb) => cb(event));
18
+ };
19
+
20
+ // src/common/isChunkError.ts
21
+ var isChunkError = (error) => {
22
+ const message = getErrorMessage(error);
23
+ if (!message) {
24
+ return false;
25
+ }
26
+ const patterns = [
27
+ /Failed to fetch dynamically imported module/i,
28
+ /Importing a module script failed/i,
29
+ /error loading dynamically imported module/i,
30
+ /Unable to preload CSS/i,
31
+ /Loading chunk \d+ failed/i,
32
+ /Loading CSS chunk \d+ failed/i,
33
+ /ChunkLoadError/i,
34
+ /Failed to fetch/i,
35
+ /NaN/i
36
+ ];
37
+ return patterns.some((pattern) => pattern.test(message));
38
+ };
39
+ var getErrorMessage = (error) => {
40
+ if (error instanceof Error) {
41
+ return error.message;
42
+ }
43
+ if (typeof error === "string") {
44
+ return error;
45
+ }
46
+ if (error && typeof error === "object" && "message" in error) {
47
+ return String(error.message);
48
+ }
49
+ if (error && typeof error === "object" && "reason" in error) {
50
+ return getErrorMessage(error.reason);
51
+ }
52
+ return null;
53
+ };
54
+
55
+ // src/common/log.ts
56
+ var logMessage = (msg) => `[spa-guard] ${msg}`;
57
+
58
+ // src/common/fallbackHtml.generated.ts
59
+ var defaultFallbackHtml = `<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif"><div style="text-align:center"><h1>Something went wrong</h1><p>Please refresh the page to continue.</p><button onclick="location.reload()">Refresh Page</button></div></div>`;
60
+
61
+ // src/common/options.ts
62
+ var defaultOptions = {
63
+ fallbackHtml: defaultFallbackHtml,
64
+ reloadDelays: [1e3, 2e3, 5e3],
65
+ useRetryId: true
66
+ };
67
+ var getOptions = () => {
68
+ const windowOptions = globalThis.window?.[optionsWindowKey];
69
+ return {
70
+ ...defaultOptions,
71
+ ...windowOptions,
72
+ reportBeacon: {
73
+ ...defaultOptions.reportBeacon,
74
+ ...windowOptions?.reportBeacon
75
+ }
76
+ };
77
+ };
78
+
79
+ // src/common/sendBeacon.ts
80
+ var sendBeacon = (beacon) => {
81
+ const options = getOptions();
82
+ if (!options.reportBeacon?.endpoint) {
83
+ console.warn(logMessage("Report endpoint is not configured"));
84
+ return;
85
+ }
86
+ const body = JSON.stringify(beacon);
87
+ const isSendBeaconAvailable = typeof globalThis.window?.navigator.sendBeacon === "function";
88
+ const isSentBeacon = isSendBeaconAvailable && navigator.sendBeacon(options.reportBeacon.endpoint, body);
89
+ if (!isSentBeacon) {
90
+ fetch(options.reportBeacon.endpoint, { body, keepalive: true, method: "POST" }).catch(
91
+ (error) => {
92
+ console.error(logMessage("Failed to send beacon:"), error);
93
+ }
94
+ );
95
+ }
96
+ };
97
+
98
+ // src/common/reload.ts
99
+ var getRetryStateFromUrl = () => {
100
+ try {
101
+ const params = new URLSearchParams(window.location.search);
102
+ const retryId = params.get(RETRY_ID_PARAM);
103
+ const retryAttempt = params.get(RETRY_ATTEMPT_PARAM);
104
+ if (retryId && retryAttempt) {
105
+ return {
106
+ retryAttempt: parseInt(retryAttempt, 10),
107
+ retryId
108
+ };
109
+ }
110
+ return null;
111
+ } catch {
112
+ return null;
113
+ }
114
+ };
115
+ var generateRetryId = () => {
116
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
117
+ return crypto.randomUUID();
118
+ }
119
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
120
+ const array = new Uint32Array(2);
121
+ crypto.getRandomValues(array);
122
+ return `${Date.now()}-${array[0].toString(36)}${array[1].toString(36)}`;
123
+ }
124
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 15)}`;
125
+ };
126
+ var buildReloadUrl = (retryId, retryAttempt) => {
127
+ const url = new URL(window.location.href);
128
+ url.searchParams.set(RETRY_ID_PARAM, retryId);
129
+ url.searchParams.set(RETRY_ATTEMPT_PARAM, String(retryAttempt));
130
+ return url.toString();
131
+ };
132
+ var attemptReload = (error) => {
133
+ const options = getOptions();
134
+ const reloadDelays = options.reloadDelays ?? [1e3, 2e3, 5e3];
135
+ const useRetryId = options.useRetryId ?? true;
136
+ const retryState = useRetryId ? getRetryStateFromUrl() : null;
137
+ const currentAttempt = retryState ? retryState.retryAttempt : 0;
138
+ if (currentAttempt >= reloadDelays.length) {
139
+ console.error(logMessage("All reload attempts exhausted"), error);
140
+ sendBeacon({
141
+ errorMessage: "Exceeded maximum reload attempts",
142
+ eventName: "chunk_error_max_reloads",
143
+ serialized: JSON.stringify({
144
+ error: String(error),
145
+ retryAttempt: currentAttempt,
146
+ retryId: retryState?.retryId
147
+ })
148
+ });
149
+ showFallbackUI();
150
+ return;
151
+ }
152
+ const nextAttempt = currentAttempt + 1;
153
+ const retryId = retryState?.retryId ?? generateRetryId();
154
+ const delay = reloadDelays[currentAttempt];
155
+ console.warn(
156
+ logMessage(
157
+ `Reload attempt ${nextAttempt}/${reloadDelays.length} in ${delay}ms (retryId: ${retryId})`
158
+ ),
159
+ error
160
+ );
161
+ setTimeout(() => {
162
+ if (useRetryId) {
163
+ const reloadUrl = buildReloadUrl(retryId, nextAttempt);
164
+ window.location.href = reloadUrl;
165
+ } else {
166
+ window.location.reload();
167
+ }
168
+ }, delay);
169
+ };
170
+ var showFallbackUI = () => {
171
+ const options = getOptions();
172
+ const fallbackHtml = options.fallbackHtml;
173
+ if (!fallbackHtml) {
174
+ console.error(logMessage("No fallback UI configured"));
175
+ return;
176
+ }
177
+ try {
178
+ document.body.innerHTML = fallbackHtml;
179
+ } catch (error) {
180
+ console.error(logMessage("Failed to inject fallback UI"), error);
181
+ }
182
+ };
183
+
184
+ // src/common/serializeError.ts
185
+ var serializeError = (error) => {
186
+ try {
187
+ const serialized = serializeErrorInternal(error);
188
+ return JSON.stringify(serialized, null, 2);
189
+ } catch {
190
+ return JSON.stringify({
191
+ error: "Failed to serialize error",
192
+ fallback: String(error)
193
+ });
194
+ }
195
+ };
196
+ var serializeErrorInternal = (error) => {
197
+ if (error === null || error === void 0) {
198
+ return { type: "null", value: error };
199
+ }
200
+ if (typeof error !== "object") {
201
+ return { type: typeof error, value: error };
202
+ }
203
+ if (error instanceof Error) {
204
+ return {
205
+ message: error.message,
206
+ name: error.name,
207
+ stack: error.stack,
208
+ type: "Error",
209
+ ...extractErrorProperties(error)
210
+ };
211
+ }
212
+ if ("reason" in error && "promise" in error) {
213
+ return {
214
+ reason: serializeErrorInternal(error.reason),
215
+ type: "PromiseRejectionEvent"
216
+ };
217
+ }
218
+ if ("error" in error && "message" in error && "filename" in error) {
219
+ return {
220
+ colno: error.colno,
221
+ error: serializeErrorInternal(error.error),
222
+ filename: error.filename,
223
+ lineno: error.lineno,
224
+ message: error.message,
225
+ type: "ErrorEvent"
226
+ };
227
+ }
228
+ if ("violatedDirective" in error && "blockedURI" in error) {
229
+ const evt = error;
230
+ return {
231
+ blockedURI: evt.blockedURI,
232
+ columnNumber: evt.columnNumber,
233
+ effectiveDirective: evt.effectiveDirective,
234
+ lineNumber: evt.lineNumber,
235
+ originalPolicy: evt.originalPolicy,
236
+ sourceFile: evt.sourceFile,
237
+ type: "SecurityPolicyViolationEvent",
238
+ violatedDirective: evt.violatedDirective
239
+ };
240
+ }
241
+ if ("type" in error && "target" in error) {
242
+ const evt = error;
243
+ return {
244
+ eventType: evt.type,
245
+ target: extractEventTarget(evt.target),
246
+ timeStamp: evt.timeStamp,
247
+ type: "Event"
248
+ };
249
+ }
250
+ return {
251
+ type: "object",
252
+ value: extractOwnProperties(error)
253
+ };
254
+ };
255
+ var extractErrorProperties = (error) => {
256
+ const props = {};
257
+ for (const key of Object.getOwnPropertyNames(error)) {
258
+ if (!["message", "name", "stack"].includes(key)) {
259
+ try {
260
+ props[key] = error[key];
261
+ } catch {
262
+ }
263
+ }
264
+ }
265
+ return props;
266
+ };
267
+ var extractEventTarget = (target) => {
268
+ if (!target) {
269
+ return null;
270
+ }
271
+ if (target instanceof HTMLElement) {
272
+ return {
273
+ className: target.className,
274
+ href: target.href,
275
+ id: target.id,
276
+ src: target.src,
277
+ tagName: target.tagName
278
+ };
279
+ }
280
+ return { type: String(target) };
281
+ };
282
+ var extractOwnProperties = (obj) => {
283
+ const props = {};
284
+ for (const key of Object.keys(obj)) {
285
+ try {
286
+ const value = obj[key];
287
+ props[key] = typeof value === "object" ? String(value) : value;
288
+ } catch {
289
+ }
290
+ }
291
+ return props;
292
+ };
293
+
294
+ // src/common/listen/internal.ts
295
+ var listenInternal = () => {
296
+ emitEvent({ name: "test" });
297
+ const wa = window.addEventListener;
298
+ wa(
299
+ "error",
300
+ (event) => {
301
+ console.error(logMessage("error:capture:"), event);
302
+ if (isChunkError(event)) {
303
+ event.preventDefault();
304
+ attemptReload(event);
305
+ return;
306
+ }
307
+ const serialized = serializeError(event);
308
+ sendBeacon({
309
+ errorMessage: event.message,
310
+ eventName: "error",
311
+ serialized
312
+ });
313
+ },
314
+ true
315
+ );
316
+ wa("error", (event) => {
317
+ console.error(logMessage("error:"), event);
318
+ });
319
+ wa("unhandledrejection", (event) => {
320
+ console.error(logMessage("unhandledrejection:"), event);
321
+ if (isChunkError(event.reason)) {
322
+ event.preventDefault();
323
+ attemptReload(event.reason);
324
+ return;
325
+ }
326
+ const serialized = serializeError(event);
327
+ sendBeacon({
328
+ errorMessage: String(event.reason),
329
+ eventName: "unhandledrejection",
330
+ serialized
331
+ });
332
+ });
333
+ wa("uncaughtException", (event) => {
334
+ console.error(logMessage("uncaughtException:"), event);
335
+ const serialized = serializeError(event);
336
+ sendBeacon({
337
+ eventName: "uncaughtException",
338
+ serialized
339
+ });
340
+ });
341
+ wa("securitypolicyviolation", (event) => {
342
+ console.error(logMessage("CSP violation:"), event.blockedURI, event.violatedDirective);
343
+ const serialized = serializeError(event);
344
+ sendBeacon({
345
+ eventMessage: `${event.violatedDirective}: ${event.blockedURI}`,
346
+ eventName: "securitypolicyviolation",
347
+ serialized
348
+ });
349
+ });
350
+ wa("vite:preloadError", (event) => {
351
+ console.error(logMessage("vite:preloadError:"), event);
352
+ event.preventDefault();
353
+ attemptReload(event);
354
+ });
355
+ };
356
+
357
+ // src/inline/index.ts
358
+ (() => {
359
+ listenInternal();
360
+ })();
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@ovineko/spa-guard",
3
+ "version": "0.0.1-alpha-1",
4
+ "description": "Chunk load error handling for SPAs with automatic recovery, beacon reporting, and deployment monitoring",
5
+ "keywords": [
6
+ "spa",
7
+ "chunk-load-error",
8
+ "vite",
9
+ "react",
10
+ "react-router",
11
+ "error-handling",
12
+ "error-boundary",
13
+ "deployment",
14
+ "beacon",
15
+ "monitoring",
16
+ "fastify",
17
+ "chunk",
18
+ "preload",
19
+ "dynamic-import"
20
+ ],
21
+ "homepage": "https://github.com/ovineko/ovineko/tree/main/packages/spa-guard",
22
+ "bugs": {
23
+ "url": "https://github.com/ovineko/ovineko/issues"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/ovineko/ovineko.git",
28
+ "directory": "packages/spa-guard"
29
+ },
30
+ "license": "MIT",
31
+ "author": "Alexander Svinarev <shibanet0@gmail.com> (shibanet0.com)",
32
+ "type": "module",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/common/index.d.ts",
36
+ "default": "./dist/common/index.js"
37
+ },
38
+ "./schema": {
39
+ "types": "./dist/schema/index.d.ts",
40
+ "default": "./dist/schema/index.js"
41
+ },
42
+ "./schema/parse": {
43
+ "types": "./dist/schema/parse.d.ts",
44
+ "default": "./dist/schema/parse.js"
45
+ },
46
+ "./react": {
47
+ "types": "./dist/react/index.d.ts",
48
+ "default": "./dist/react/index.js"
49
+ },
50
+ "./react-router": {
51
+ "types": "./dist/react-router/index.d.ts",
52
+ "default": "./dist/react-router/index.js"
53
+ },
54
+ "./fastify": {
55
+ "types": "./dist/fastify/index.d.ts",
56
+ "default": "./dist/fastify/index.js"
57
+ },
58
+ "./vite-plugin": {
59
+ "types": "./dist/vite-plugin/index.d.ts",
60
+ "default": "./dist/vite-plugin/index.js"
61
+ },
62
+ "./react-error-boundary": {
63
+ "types": "./dist/react-error-boundary/index.d.ts",
64
+ "default": "./dist/react-error-boundary/index.js"
65
+ }
66
+ },
67
+ "files": [
68
+ "dist",
69
+ "dist-inline",
70
+ "dist-inline-trace",
71
+ "README.md"
72
+ ],
73
+ "peerDependencies": {
74
+ "react": "^19",
75
+ "react-error-boundary": "^6",
76
+ "react-router": "^7",
77
+ "typebox": "^1",
78
+ "vite": "^8 || ^7"
79
+ },
80
+ "engines": {
81
+ "node": ">=20"
82
+ },
83
+ "publishConfig": {
84
+ "access": "public"
85
+ }
86
+ }