@oluso/nextjs 1.0.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 +178 -0
- package/dist/api-route.d.ts +15 -0
- package/dist/api-route.js +42 -0
- package/dist/client.d.ts +37 -0
- package/dist/client.js +149 -0
- package/dist/context.d.ts +25 -0
- package/dist/context.js +79 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +19 -0
- package/dist/instrumentation.d.ts +48 -0
- package/dist/instrumentation.js +83 -0
- package/dist/middleware.d.ts +15 -0
- package/dist/middleware.js +38 -0
- package/dist/queue.d.ts +21 -0
- package/dist/queue.js +55 -0
- package/dist/request-context.d.ts +15 -0
- package/dist/request-context.js +41 -0
- package/dist/route-handler.d.ts +20 -0
- package/dist/route-handler.js +49 -0
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +47 -0
- package/dist/transport.d.ts +14 -0
- package/dist/transport.js +39 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +2 -0
- package/dist/use-oluso-error-effect.d.ts +28 -0
- package/dist/use-oluso-error-effect.js +41 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Ezeh
|
|
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
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @oluso/nextjs
|
|
2
|
+
|
|
3
|
+
AI-powered error monitoring for Next.js applications: server-side capture for the App Router, Pages Router, and Middleware, plus React bindings for Client Components. Works unchanged on both the Node.js and Edge runtimes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @oluso/nextjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Next.js `>=13.4.0` and React `>=18.2.0`.
|
|
12
|
+
|
|
13
|
+
## Recommended setup: `instrumentation.ts`
|
|
14
|
+
|
|
15
|
+
Next.js's [`instrumentation.ts`](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) is the one place guaranteed to run exactly once per server instance, and its `onRequestError` hook is called for errors from Server Components, Route Handlers, Server Actions, and Middleware — whether or not you've wrapped them individually. This is the fastest path to full coverage:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// instrumentation.ts
|
|
19
|
+
import { Oluso, registerOlusoProcessHandlers, createOnRequestError } from '@oluso/nextjs';
|
|
20
|
+
|
|
21
|
+
export const oluso = new Oluso({
|
|
22
|
+
apiKey: process.env.OLUSO_API_KEY!,
|
|
23
|
+
environment: process.env.NODE_ENV,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export async function register() {
|
|
27
|
+
registerOlusoProcessHandlers(oluso); // uncaughtException / unhandledRejection, Node runtime only
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const onRequestError = createOnRequestError(oluso);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That's the whole server-side integration for a basic setup. The `with*` wrappers below are opt-in, for routes where you also want request-scoped breadcrumbs recorded *during* the handler, not just the failure itself.
|
|
34
|
+
|
|
35
|
+
## App Router route handlers
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// app/api/widgets/route.ts
|
|
39
|
+
import { withOluso } from '@oluso/nextjs';
|
|
40
|
+
import { oluso } from '../../../instrumentation';
|
|
41
|
+
|
|
42
|
+
export const GET = withOluso(oluso, async (req) => {
|
|
43
|
+
oluso.addBreadcrumb({ message: 'fetching widgets', category: 'db' });
|
|
44
|
+
const widgets = await db.widgets.findMany();
|
|
45
|
+
return Response.json(widgets);
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Wrap each exported method (`GET`, `POST`, ...) separately. Thrown errors and 5xx responses are reported with the request's method, URL, and sanitized headers attached; the request body is never read here (it's a stream your handler needs to consume itself).
|
|
50
|
+
|
|
51
|
+
## Pages Router API routes
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// pages/api/widgets.ts
|
|
55
|
+
import { withOlusoApiRoute } from '@oluso/nextjs';
|
|
56
|
+
import { oluso } from '../../instrumentation';
|
|
57
|
+
|
|
58
|
+
export default withOlusoApiRoute(oluso, async (req, res) => {
|
|
59
|
+
const widgets = await db.widgets.findMany();
|
|
60
|
+
res.json(widgets);
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Middleware
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// middleware.ts
|
|
68
|
+
import { withOlusoMiddleware } from '@oluso/nextjs';
|
|
69
|
+
import { oluso } from './instrumentation';
|
|
70
|
+
|
|
71
|
+
export const middleware = withOlusoMiddleware(oluso, async (req) => {
|
|
72
|
+
...
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Middleware always runs on the Edge runtime — this wrapper (and the rest of the server client) has no dependency on Node's `http`/`os` modules, so it works there unchanged.
|
|
77
|
+
|
|
78
|
+
## Client Components
|
|
79
|
+
|
|
80
|
+
Import from the `/client` subpath, which carries its own `'use client'` directive — importing these from the main entry point would pull server-only code into the client bundle.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// app/layout.tsx
|
|
84
|
+
import { OlusoProvider } from '@oluso/nextjs/client';
|
|
85
|
+
|
|
86
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
87
|
+
return (
|
|
88
|
+
<html>
|
|
89
|
+
<body>
|
|
90
|
+
<OlusoProvider options={{ apiKey: process.env.NEXT_PUBLIC_OLUSO_API_KEY! }}>
|
|
91
|
+
{children}
|
|
92
|
+
</OlusoProvider>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
// app/dashboard/error.tsx
|
|
101
|
+
'use client';
|
|
102
|
+
import { useOlusoErrorEffect } from '@oluso/nextjs/client';
|
|
103
|
+
|
|
104
|
+
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
105
|
+
useOlusoErrorEffect(error); // reports once per distinct error, including error.digest
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
Something went wrong.
|
|
109
|
+
<button onClick={reset}>Try again</button>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`useOlusoErrorEffect` needs an `<OlusoProvider>` above it in the tree, which rules out `global-error.tsx` specifically — it replaces the root layout the provider would normally live in. For that file, construct an `OlusoClient` directly instead:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// app/global-error.tsx
|
|
119
|
+
'use client';
|
|
120
|
+
import { OlusoClient } from '@oluso/nextjs/client';
|
|
121
|
+
import { useEffect } from 'react';
|
|
122
|
+
|
|
123
|
+
const client = new OlusoClient({ apiKey: process.env.NEXT_PUBLIC_OLUSO_API_KEY! });
|
|
124
|
+
|
|
125
|
+
export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
client.captureException(error);
|
|
128
|
+
}, [error]);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<html>
|
|
132
|
+
<body>Something went seriously wrong.</body>
|
|
133
|
+
</html>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`useOluso()` and `ErrorBoundary` are also re-exported from `@oluso/nextjs/client` for manual reporting and non-route error boundaries, same as `@oluso/react`.
|
|
139
|
+
|
|
140
|
+
## Breadcrumbs & User Context (server-side)
|
|
141
|
+
|
|
142
|
+
`oluso.addBreadcrumb`/`setUserContext`/`setCustomContext` are scoped to the current request when called inside a `with*` wrapper (via `AsyncLocalStorage`), so concurrent requests on a self-hosted, long-running Next.js server don't bleed context into each other:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
export const POST = withOluso(oluso, async (req) => {
|
|
146
|
+
const user = await getUser(req);
|
|
147
|
+
oluso.setUserContext({ id: user.id });
|
|
148
|
+
oluso.addBreadcrumb({ message: 'checkout started', category: 'action' });
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await doCheckout();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
oluso.captureException(err, { step: 'checkout' });
|
|
154
|
+
return Response.json({ error: 'checkout failed' }, { status: 500 });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return Response.json({ ok: true });
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Advanced Configuration
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
new Oluso({
|
|
165
|
+
apiKey: 'your-api-key',
|
|
166
|
+
endpoint: 'https://api.oluso.dev/api/v1/error/report', // override for self-hosting
|
|
167
|
+
environment: 'staging',
|
|
168
|
+
defaultSeverity: 'medium',
|
|
169
|
+
maxBreadcrumbs: 50,
|
|
170
|
+
maxErrorsPerMinute: 100,
|
|
171
|
+
sensitiveKeys: ['ssn', 'internal_id'],
|
|
172
|
+
shouldReport: (err) => !err.message.includes('expected'),
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NextApiHandler } from 'next';
|
|
2
|
+
import { Oluso } from './client';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a Pages Router API route (`pages/api/**\/*.ts`). Reports thrown
|
|
5
|
+
* errors and 5xx responses, with a request-scoped breadcrumb trail around
|
|
6
|
+
* the handler call.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // pages/api/widgets.ts
|
|
10
|
+
* export default withOlusoApiRoute(oluso, async (req, res) => {
|
|
11
|
+
* ...
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function withOlusoApiRoute(oluso: Oluso, handler: NextApiHandler): NextApiHandler;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withOlusoApiRoute = withOlusoApiRoute;
|
|
4
|
+
const request_context_1 = require("./request-context");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a Pages Router API route (`pages/api/**\/*.ts`). Reports thrown
|
|
7
|
+
* errors and 5xx responses, with a request-scoped breadcrumb trail around
|
|
8
|
+
* the handler call.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* // pages/api/widgets.ts
|
|
12
|
+
* export default withOlusoApiRoute(oluso, async (req, res) => {
|
|
13
|
+
* ...
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function withOlusoApiRoute(oluso, handler) {
|
|
18
|
+
return function olusoApiHandler(req, res) {
|
|
19
|
+
return oluso.runInContext(async () => {
|
|
20
|
+
oluso.addBreadcrumb({
|
|
21
|
+
message: `${req.method} ${req.url}`,
|
|
22
|
+
level: 'info',
|
|
23
|
+
category: 'http',
|
|
24
|
+
data: { method: req.method, url: req.url },
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
await handler(req, res);
|
|
28
|
+
if (res.statusCode >= 500) {
|
|
29
|
+
const error = new Error(`Server error: ${res.statusCode} - ${req.method} ${req.url}`);
|
|
30
|
+
error.severity = 'critical';
|
|
31
|
+
await oluso.reportError(error, (0, request_context_1.fromApiRequest)(req));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
36
|
+
err.severity = 'critical';
|
|
37
|
+
await oluso.reportError(err, (0, request_context_1.fromApiRequest)(req));
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { UserContext } from '@oluso/core';
|
|
2
|
+
import { NextjsContextManager } from './context';
|
|
3
|
+
import { OlusoNextjsOptions, RequestContext } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Server-side Oluso client for Next.js. Unlike @oluso/node's `Oluso`, this
|
|
6
|
+
* has no dependency on Node's `http`/`https`/`os` modules or on
|
|
7
|
+
* `process.on(...)` at construction time -- it works unchanged on both the
|
|
8
|
+
* Node.js and Edge runtimes Next.js can run route handlers, middleware, and
|
|
9
|
+
* Server Components on. For Client Components, use the browser-backed
|
|
10
|
+
* pieces exported from `@oluso/nextjs/client` instead.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Oluso {
|
|
13
|
+
private options;
|
|
14
|
+
private endpoint;
|
|
15
|
+
private contextManager;
|
|
16
|
+
private sanitizer;
|
|
17
|
+
private rateLimiter;
|
|
18
|
+
private offlineQueue;
|
|
19
|
+
constructor(options: OlusoNextjsOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Runs `callback` inside a fresh request-scoped context. Route/middleware
|
|
22
|
+
* wrappers in this package call this once per request; call it yourself
|
|
23
|
+
* if you're reporting from somewhere those wrappers don't cover.
|
|
24
|
+
*/
|
|
25
|
+
runInContext<T>(callback: () => T): T;
|
|
26
|
+
addBreadcrumb(breadcrumb: Parameters<NextjsContextManager['addBreadcrumb']>[0]): void;
|
|
27
|
+
setUserContext(user: UserContext): void;
|
|
28
|
+
setCustomContext(key: string, value: any): void;
|
|
29
|
+
captureException(error: Error, customContext?: Record<string, any>): Promise<void>;
|
|
30
|
+
flush(): Promise<void>;
|
|
31
|
+
reportError(error: Error, request?: RequestContext): Promise<void>;
|
|
32
|
+
private buildErrorContext;
|
|
33
|
+
private sanitizeRequest;
|
|
34
|
+
private sendReport;
|
|
35
|
+
private generateErrorTitle;
|
|
36
|
+
}
|
|
37
|
+
export default Oluso;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Oluso = void 0;
|
|
4
|
+
const core_1 = require("@oluso/core");
|
|
5
|
+
const transport_1 = require("./transport");
|
|
6
|
+
const queue_1 = require("./queue");
|
|
7
|
+
const context_1 = require("./context");
|
|
8
|
+
const runtime_1 = require("./runtime");
|
|
9
|
+
const DEFAULT_ENDPOINT = 'https://api.oluso.dev/api/v1/error/report';
|
|
10
|
+
/**
|
|
11
|
+
* Server-side Oluso client for Next.js. Unlike @oluso/node's `Oluso`, this
|
|
12
|
+
* has no dependency on Node's `http`/`https`/`os` modules or on
|
|
13
|
+
* `process.on(...)` at construction time -- it works unchanged on both the
|
|
14
|
+
* Node.js and Edge runtimes Next.js can run route handlers, middleware, and
|
|
15
|
+
* Server Components on. For Client Components, use the browser-backed
|
|
16
|
+
* pieces exported from `@oluso/nextjs/client` instead.
|
|
17
|
+
*/
|
|
18
|
+
class Oluso {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = {
|
|
21
|
+
logToConsole: true,
|
|
22
|
+
timeout: 5000,
|
|
23
|
+
environment: 'production',
|
|
24
|
+
defaultSeverity: 'medium',
|
|
25
|
+
maxBreadcrumbs: 30,
|
|
26
|
+
enableOfflineQueue: true,
|
|
27
|
+
maxQueueSize: 100,
|
|
28
|
+
maxErrorsPerMinute: 60,
|
|
29
|
+
...options,
|
|
30
|
+
};
|
|
31
|
+
this.endpoint = this.options.endpoint || DEFAULT_ENDPOINT;
|
|
32
|
+
this.contextManager = new context_1.NextjsContextManager(this.options.maxBreadcrumbs);
|
|
33
|
+
this.sanitizer = new core_1.Sanitizer(this.options.sensitiveKeys);
|
|
34
|
+
this.rateLimiter = new core_1.RateLimiter(this.options.maxErrorsPerMinute);
|
|
35
|
+
this.offlineQueue = new queue_1.OfflineQueue(this.options.maxQueueSize);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runs `callback` inside a fresh request-scoped context. Route/middleware
|
|
39
|
+
* wrappers in this package call this once per request; call it yourself
|
|
40
|
+
* if you're reporting from somewhere those wrappers don't cover.
|
|
41
|
+
*/
|
|
42
|
+
runInContext(callback) {
|
|
43
|
+
return this.contextManager.run(callback);
|
|
44
|
+
}
|
|
45
|
+
addBreadcrumb(breadcrumb) {
|
|
46
|
+
this.contextManager.addBreadcrumb(breadcrumb);
|
|
47
|
+
}
|
|
48
|
+
setUserContext(user) {
|
|
49
|
+
this.contextManager.setUserContext(user);
|
|
50
|
+
}
|
|
51
|
+
setCustomContext(key, value) {
|
|
52
|
+
this.contextManager.setCustomContext(key, value);
|
|
53
|
+
}
|
|
54
|
+
captureException(error, customContext) {
|
|
55
|
+
if (customContext) {
|
|
56
|
+
for (const [key, value] of Object.entries(customContext)) {
|
|
57
|
+
this.setCustomContext(key, value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return this.reportError(error);
|
|
61
|
+
}
|
|
62
|
+
async flush() {
|
|
63
|
+
await this.offlineQueue.processQueue((report) => (0, transport_1.sendErrorReport)(this.endpoint, report, {
|
|
64
|
+
apiKey: this.options.apiKey,
|
|
65
|
+
timeout: this.options.timeout,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
reportError(error, request) {
|
|
69
|
+
if (this.options.shouldReport && !this.options.shouldReport(error)) {
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
}
|
|
72
|
+
if (!this.rateLimiter.canSend()) {
|
|
73
|
+
if (this.options.logToConsole) {
|
|
74
|
+
console.warn('[Oluso] Rate limit exceeded, error not reported');
|
|
75
|
+
}
|
|
76
|
+
return Promise.resolve();
|
|
77
|
+
}
|
|
78
|
+
if (this.options.logToConsole) {
|
|
79
|
+
console.error('[Oluso]', error);
|
|
80
|
+
}
|
|
81
|
+
const context = this.buildErrorContext(request);
|
|
82
|
+
const fingerprint = this.options.fingerprint
|
|
83
|
+
? this.options.fingerprint(error, context)
|
|
84
|
+
: (0, core_1.generateFingerprint)(error, context);
|
|
85
|
+
const report = {
|
|
86
|
+
title: this.generateErrorTitle(error),
|
|
87
|
+
message: error.message,
|
|
88
|
+
stack_trace: error.stack,
|
|
89
|
+
environment: this.options.environment,
|
|
90
|
+
severity: error.severity || this.options.defaultSeverity || 'medium',
|
|
91
|
+
tags: this.options.tags || [],
|
|
92
|
+
fingerprint,
|
|
93
|
+
context,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
};
|
|
96
|
+
return this.sendReport(report);
|
|
97
|
+
}
|
|
98
|
+
buildErrorContext(request) {
|
|
99
|
+
const managed = this.contextManager.getContext();
|
|
100
|
+
return {
|
|
101
|
+
...managed,
|
|
102
|
+
custom: {
|
|
103
|
+
...managed.custom,
|
|
104
|
+
server: (0, runtime_1.getRuntimeServerContext)(),
|
|
105
|
+
...(request ? { request: this.sanitizeRequest(request) } : {}),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
sanitizeRequest(request) {
|
|
110
|
+
return {
|
|
111
|
+
...request,
|
|
112
|
+
headers: request.headers ? this.sanitizer.sanitizeHeaders(request.headers) : undefined,
|
|
113
|
+
query: request.query ? this.sanitizer.sanitizeQuery(request.query) : undefined,
|
|
114
|
+
body: request.body !== undefined ? this.sanitizer.sanitizeBody(request.body) : undefined,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async sendReport(report) {
|
|
118
|
+
try {
|
|
119
|
+
await (0, transport_1.sendErrorReport)(this.endpoint, report, {
|
|
120
|
+
apiKey: this.options.apiKey,
|
|
121
|
+
timeout: this.options.timeout,
|
|
122
|
+
});
|
|
123
|
+
if (this.options.enableOfflineQueue && !this.offlineQueue.isEmpty()) {
|
|
124
|
+
this.offlineQueue
|
|
125
|
+
.processQueue((queuedReport) => (0, transport_1.sendErrorReport)(this.endpoint, queuedReport, {
|
|
126
|
+
apiKey: this.options.apiKey,
|
|
127
|
+
timeout: this.options.timeout,
|
|
128
|
+
}))
|
|
129
|
+
.catch(() => {
|
|
130
|
+
// Silently fail queue processing
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (this.options.enableOfflineQueue) {
|
|
136
|
+
this.offlineQueue.enqueue(report);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
generateErrorTitle(error) {
|
|
141
|
+
const firstLine = error.message.split('\n')[0].trim();
|
|
142
|
+
if (firstLine && firstLine.length > 0) {
|
|
143
|
+
return firstLine.length <= 100 ? firstLine : `${firstLine.substring(0, 97)}...`;
|
|
144
|
+
}
|
|
145
|
+
return `${error.constructor.name} Error`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.Oluso = Oluso;
|
|
149
|
+
exports.default = Oluso;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Breadcrumb, ErrorContext, UserContext } from '@oluso/core';
|
|
2
|
+
/**
|
|
3
|
+
* Request-scoped breadcrumb/user/context store, the Next.js counterpart to
|
|
4
|
+
* @oluso/node's ContextManager. Route/middleware wrappers call `run()` once
|
|
5
|
+
* per request so concurrent requests on a long-running Node server (e.g.
|
|
6
|
+
* self-hosted via `next start`) don't bleed breadcrumbs into each other.
|
|
7
|
+
*
|
|
8
|
+
* Falls back to a single shared store when called outside `run()` -- e.g. a
|
|
9
|
+
* Server Component rendered without an explicit wrapper, or a call made
|
|
10
|
+
* from module scope during startup -- mirroring @oluso/browser's flat
|
|
11
|
+
* (non-request-scoped) behavior rather than silently dropping the data.
|
|
12
|
+
*/
|
|
13
|
+
export declare class NextjsContextManager {
|
|
14
|
+
private storage;
|
|
15
|
+
private globalStore;
|
|
16
|
+
private maxBreadcrumbs;
|
|
17
|
+
constructor(maxBreadcrumbs?: number);
|
|
18
|
+
run<T>(callback: () => T): T;
|
|
19
|
+
private store;
|
|
20
|
+
addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;
|
|
21
|
+
setUserContext(user: UserContext): void;
|
|
22
|
+
setCustomContext(key: string, value: any): void;
|
|
23
|
+
getContext(): Partial<ErrorContext>;
|
|
24
|
+
}
|
|
25
|
+
export default NextjsContextManager;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NextjsContextManager = void 0;
|
|
4
|
+
function createStorage() {
|
|
5
|
+
try {
|
|
6
|
+
// AsyncLocalStorage is on Vercel/Next.js's list of supported Edge
|
|
7
|
+
// Runtime APIs (Next uses it internally for next/headers), but this is
|
|
8
|
+
// guarded the same defensive way @oluso/node guards worker_threads/
|
|
9
|
+
// cluster: if some other Edge-like environment this package ends up
|
|
10
|
+
// running in doesn't have it, fall back rather than crash.
|
|
11
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
12
|
+
return new AsyncLocalStorage();
|
|
13
|
+
}
|
|
14
|
+
catch (_a) {
|
|
15
|
+
let current;
|
|
16
|
+
return {
|
|
17
|
+
run(data, callback) {
|
|
18
|
+
current = data;
|
|
19
|
+
try {
|
|
20
|
+
return callback();
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
current = undefined;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
getStore() {
|
|
27
|
+
return current;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Request-scoped breadcrumb/user/context store, the Next.js counterpart to
|
|
34
|
+
* @oluso/node's ContextManager. Route/middleware wrappers call `run()` once
|
|
35
|
+
* per request so concurrent requests on a long-running Node server (e.g.
|
|
36
|
+
* self-hosted via `next start`) don't bleed breadcrumbs into each other.
|
|
37
|
+
*
|
|
38
|
+
* Falls back to a single shared store when called outside `run()` -- e.g. a
|
|
39
|
+
* Server Component rendered without an explicit wrapper, or a call made
|
|
40
|
+
* from module scope during startup -- mirroring @oluso/browser's flat
|
|
41
|
+
* (non-request-scoped) behavior rather than silently dropping the data.
|
|
42
|
+
*/
|
|
43
|
+
class NextjsContextManager {
|
|
44
|
+
constructor(maxBreadcrumbs = 30) {
|
|
45
|
+
this.globalStore = { breadcrumbs: [], customContext: {} };
|
|
46
|
+
this.storage = createStorage();
|
|
47
|
+
this.maxBreadcrumbs = maxBreadcrumbs;
|
|
48
|
+
}
|
|
49
|
+
run(callback) {
|
|
50
|
+
return this.storage.run({ breadcrumbs: [], customContext: {} }, callback);
|
|
51
|
+
}
|
|
52
|
+
store() {
|
|
53
|
+
var _a;
|
|
54
|
+
return (_a = this.storage.getStore()) !== null && _a !== void 0 ? _a : this.globalStore;
|
|
55
|
+
}
|
|
56
|
+
addBreadcrumb(breadcrumb) {
|
|
57
|
+
const store = this.store();
|
|
58
|
+
store.breadcrumbs.push({ ...breadcrumb, timestamp: Date.now() });
|
|
59
|
+
if (store.breadcrumbs.length > this.maxBreadcrumbs) {
|
|
60
|
+
store.breadcrumbs.shift();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
setUserContext(user) {
|
|
64
|
+
this.store().userContext = user;
|
|
65
|
+
}
|
|
66
|
+
setCustomContext(key, value) {
|
|
67
|
+
this.store().customContext[key] = value;
|
|
68
|
+
}
|
|
69
|
+
getContext() {
|
|
70
|
+
const store = this.store();
|
|
71
|
+
return {
|
|
72
|
+
breadcrumbs: store.breadcrumbs,
|
|
73
|
+
user: store.userContext,
|
|
74
|
+
custom: store.customContext,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.NextjsContextManager = NextjsContextManager;
|
|
79
|
+
exports.default = NextjsContextManager;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from '@oluso/core';
|
|
2
|
+
export type { OlusoNextjsOptions, RequestContext, RuntimeServerContext } from './types';
|
|
3
|
+
export { Oluso } from './client';
|
|
4
|
+
export { withOluso } from './route-handler';
|
|
5
|
+
export type { RouteHandler } from './route-handler';
|
|
6
|
+
export { withOlusoApiRoute } from './api-route';
|
|
7
|
+
export { withOlusoMiddleware } from './middleware';
|
|
8
|
+
export { fromFetchRequest, fromApiRequest } from './request-context';
|
|
9
|
+
export { registerOlusoProcessHandlers, createOnRequestError, } from './instrumentation';
|
|
10
|
+
export type { NextRequestErrorInfo, NextRequestErrorContext } from './instrumentation';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOnRequestError = exports.registerOlusoProcessHandlers = exports.fromApiRequest = exports.fromFetchRequest = exports.withOlusoMiddleware = exports.withOlusoApiRoute = exports.withOluso = exports.Oluso = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
tslib_1.__exportStar(require("@oluso/core"), exports);
|
|
6
|
+
var client_1 = require("./client");
|
|
7
|
+
Object.defineProperty(exports, "Oluso", { enumerable: true, get: function () { return client_1.Oluso; } });
|
|
8
|
+
var route_handler_1 = require("./route-handler");
|
|
9
|
+
Object.defineProperty(exports, "withOluso", { enumerable: true, get: function () { return route_handler_1.withOluso; } });
|
|
10
|
+
var api_route_1 = require("./api-route");
|
|
11
|
+
Object.defineProperty(exports, "withOlusoApiRoute", { enumerable: true, get: function () { return api_route_1.withOlusoApiRoute; } });
|
|
12
|
+
var middleware_1 = require("./middleware");
|
|
13
|
+
Object.defineProperty(exports, "withOlusoMiddleware", { enumerable: true, get: function () { return middleware_1.withOlusoMiddleware; } });
|
|
14
|
+
var request_context_1 = require("./request-context");
|
|
15
|
+
Object.defineProperty(exports, "fromFetchRequest", { enumerable: true, get: function () { return request_context_1.fromFetchRequest; } });
|
|
16
|
+
Object.defineProperty(exports, "fromApiRequest", { enumerable: true, get: function () { return request_context_1.fromApiRequest; } });
|
|
17
|
+
var instrumentation_1 = require("./instrumentation");
|
|
18
|
+
Object.defineProperty(exports, "registerOlusoProcessHandlers", { enumerable: true, get: function () { return instrumentation_1.registerOlusoProcessHandlers; } });
|
|
19
|
+
Object.defineProperty(exports, "createOnRequestError", { enumerable: true, get: function () { return instrumentation_1.createOnRequestError; } });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Oluso } from './client';
|
|
2
|
+
/**
|
|
3
|
+
* Registers `process.on('uncaughtException'/'unhandledRejection', ...)`
|
|
4
|
+
* handlers, mirroring @oluso/node's global handlers. Call this from the
|
|
5
|
+
* `register()` export of your `instrumentation.ts` -- Next.js calls
|
|
6
|
+
* `register()` once per server instance, before any other code runs, which
|
|
7
|
+
* is the one place in a Next.js app that's guaranteed to run exactly once
|
|
8
|
+
* (a module-scope side effect in a route file can run once per route
|
|
9
|
+
* per worker, not once for the whole server).
|
|
10
|
+
*
|
|
11
|
+
* No-ops on the Edge runtime: `process.on` isn't available there, and
|
|
12
|
+
* `register()` itself already runs there too (Next.js calls it once per
|
|
13
|
+
* runtime the app uses), so this has to check rather than assume Node.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerOlusoProcessHandlers(oluso: Oluso): void;
|
|
16
|
+
export interface NextRequestErrorInfo {
|
|
17
|
+
path: string;
|
|
18
|
+
method: string;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export interface NextRequestErrorContext {
|
|
22
|
+
routerKind?: 'Pages Router' | 'App Router';
|
|
23
|
+
routePath?: string;
|
|
24
|
+
routeType?: 'render' | 'route' | 'action' | 'middleware';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Builds the `onRequestError` export for `instrumentation.ts` -- the hook
|
|
28
|
+
* Next.js (15+) calls for errors from Server Components, Route Handlers,
|
|
29
|
+
* Server Actions, and Middleware, whether or not they're individually
|
|
30
|
+
* wrapped with `withOluso`/`withOlusoMiddleware`. This is the zero-config
|
|
31
|
+
* catch-all; the `with*` wrappers exist for the cases where you also want
|
|
32
|
+
* request-scoped breadcrumbs recorded *during* the handler, not just the
|
|
33
|
+
* failure itself.
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* // instrumentation.ts
|
|
37
|
+
* import { Oluso, registerOlusoProcessHandlers, createOnRequestError } from '@oluso/nextjs';
|
|
38
|
+
*
|
|
39
|
+
* const oluso = new Oluso({ apiKey: process.env.OLUSO_API_KEY! });
|
|
40
|
+
*
|
|
41
|
+
* export async function register() {
|
|
42
|
+
* registerOlusoProcessHandlers(oluso);
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* export const onRequestError = createOnRequestError(oluso);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function createOnRequestError(oluso: Oluso): (error: unknown, request: NextRequestErrorInfo, context: NextRequestErrorContext) => Promise<void>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerOlusoProcessHandlers = registerOlusoProcessHandlers;
|
|
4
|
+
exports.createOnRequestError = createOnRequestError;
|
|
5
|
+
const runtime_1 = require("./runtime");
|
|
6
|
+
/**
|
|
7
|
+
* Registers `process.on('uncaughtException'/'unhandledRejection', ...)`
|
|
8
|
+
* handlers, mirroring @oluso/node's global handlers. Call this from the
|
|
9
|
+
* `register()` export of your `instrumentation.ts` -- Next.js calls
|
|
10
|
+
* `register()` once per server instance, before any other code runs, which
|
|
11
|
+
* is the one place in a Next.js app that's guaranteed to run exactly once
|
|
12
|
+
* (a module-scope side effect in a route file can run once per route
|
|
13
|
+
* per worker, not once for the whole server).
|
|
14
|
+
*
|
|
15
|
+
* No-ops on the Edge runtime: `process.on` isn't available there, and
|
|
16
|
+
* `register()` itself already runs there too (Next.js calls it once per
|
|
17
|
+
* runtime the app uses), so this has to check rather than assume Node.
|
|
18
|
+
*/
|
|
19
|
+
function registerOlusoProcessHandlers(oluso) {
|
|
20
|
+
if (typeof process === 'undefined' || typeof process.on !== 'function')
|
|
21
|
+
return;
|
|
22
|
+
if ((0, runtime_1.getRuntime)() !== 'nodejs')
|
|
23
|
+
return;
|
|
24
|
+
process.on('uncaughtException', (error) => {
|
|
25
|
+
oluso.addBreadcrumb({
|
|
26
|
+
message: 'Uncaught exception occurred',
|
|
27
|
+
level: 'error',
|
|
28
|
+
category: 'error',
|
|
29
|
+
});
|
|
30
|
+
error.severity = 'critical';
|
|
31
|
+
oluso.reportError(error);
|
|
32
|
+
});
|
|
33
|
+
process.on('unhandledRejection', (reason) => {
|
|
34
|
+
oluso.addBreadcrumb({
|
|
35
|
+
message: 'Unhandled promise rejection',
|
|
36
|
+
level: 'error',
|
|
37
|
+
category: 'promise',
|
|
38
|
+
});
|
|
39
|
+
const error = reason instanceof Error ? reason : new Error(`Unhandled rejection: ${String(reason)}`);
|
|
40
|
+
error.severity = 'critical';
|
|
41
|
+
oluso.reportError(error);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Builds the `onRequestError` export for `instrumentation.ts` -- the hook
|
|
46
|
+
* Next.js (15+) calls for errors from Server Components, Route Handlers,
|
|
47
|
+
* Server Actions, and Middleware, whether or not they're individually
|
|
48
|
+
* wrapped with `withOluso`/`withOlusoMiddleware`. This is the zero-config
|
|
49
|
+
* catch-all; the `with*` wrappers exist for the cases where you also want
|
|
50
|
+
* request-scoped breadcrumbs recorded *during* the handler, not just the
|
|
51
|
+
* failure itself.
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* // instrumentation.ts
|
|
55
|
+
* import { Oluso, registerOlusoProcessHandlers, createOnRequestError } from '@oluso/nextjs';
|
|
56
|
+
*
|
|
57
|
+
* const oluso = new Oluso({ apiKey: process.env.OLUSO_API_KEY! });
|
|
58
|
+
*
|
|
59
|
+
* export async function register() {
|
|
60
|
+
* registerOlusoProcessHandlers(oluso);
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* export const onRequestError = createOnRequestError(oluso);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function createOnRequestError(oluso) {
|
|
67
|
+
return async function onRequestError(error, request, context) {
|
|
68
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
69
|
+
err.severity = 'critical';
|
|
70
|
+
await oluso.reportError(err, {
|
|
71
|
+
url: request.path,
|
|
72
|
+
method: request.method,
|
|
73
|
+
headers: request.headers,
|
|
74
|
+
routeType: context.routeType === 'route'
|
|
75
|
+
? 'route-handler'
|
|
76
|
+
: context.routeType === 'action'
|
|
77
|
+
? 'action'
|
|
78
|
+
: context.routeType === 'middleware'
|
|
79
|
+
? 'middleware'
|
|
80
|
+
: 'render',
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NextMiddleware } from 'next/server';
|
|
2
|
+
import { Oluso } from './client';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps Next.js middleware (`middleware.ts`), which always runs on the Edge
|
|
5
|
+
* runtime. Reports thrown errors with a request-scoped breadcrumb trail,
|
|
6
|
+
* then re-throws so Next.js's own handling of the failure is unchanged.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // middleware.ts
|
|
10
|
+
* export const middleware = withOlusoMiddleware(oluso, async (req) => {
|
|
11
|
+
* ...
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function withOlusoMiddleware(oluso: Oluso, handler: NextMiddleware): NextMiddleware;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withOlusoMiddleware = withOlusoMiddleware;
|
|
4
|
+
const request_context_1 = require("./request-context");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps Next.js middleware (`middleware.ts`), which always runs on the Edge
|
|
7
|
+
* runtime. Reports thrown errors with a request-scoped breadcrumb trail,
|
|
8
|
+
* then re-throws so Next.js's own handling of the failure is unchanged.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* // middleware.ts
|
|
12
|
+
* export const middleware = withOlusoMiddleware(oluso, async (req) => {
|
|
13
|
+
* ...
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function withOlusoMiddleware(oluso, handler) {
|
|
18
|
+
return function olusoMiddleware(req, event) {
|
|
19
|
+
return oluso.runInContext(async () => {
|
|
20
|
+
const url = new URL(req.url);
|
|
21
|
+
oluso.addBreadcrumb({
|
|
22
|
+
message: `${req.method} ${url.pathname}`,
|
|
23
|
+
level: 'info',
|
|
24
|
+
category: 'http',
|
|
25
|
+
data: { method: req.method, url: url.pathname },
|
|
26
|
+
});
|
|
27
|
+
try {
|
|
28
|
+
return await handler(req, event);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
32
|
+
err.severity = 'critical';
|
|
33
|
+
await oluso.reportError(err, (0, request_context_1.fromFetchRequest)(req));
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ErrorReport } from '@oluso/core';
|
|
2
|
+
/**
|
|
3
|
+
* In-memory-only offline queue. Unlike @oluso/browser's queue, this has
|
|
4
|
+
* nothing to persist to (no localStorage on the server, and no disk write
|
|
5
|
+
* that would be safe to do implicitly on every request) -- it only smooths
|
|
6
|
+
* over a send failure that gets retried by a *later* error in the same warm
|
|
7
|
+
* process/instance. On serverless platforms where each invocation is a
|
|
8
|
+
* fresh process, a queued report that never gets a follow-up error to
|
|
9
|
+
* piggyback on is simply lost, same as it would be if reporting weren't
|
|
10
|
+
* retried at all.
|
|
11
|
+
*/
|
|
12
|
+
export declare class OfflineQueue {
|
|
13
|
+
private queue;
|
|
14
|
+
private maxSize;
|
|
15
|
+
private isProcessing;
|
|
16
|
+
constructor(maxSize?: number);
|
|
17
|
+
enqueue(report: ErrorReport): void;
|
|
18
|
+
isEmpty(): boolean;
|
|
19
|
+
processQueue(sendFn: (report: ErrorReport) => Promise<void>): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export default OfflineQueue;
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OfflineQueue = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* In-memory-only offline queue. Unlike @oluso/browser's queue, this has
|
|
6
|
+
* nothing to persist to (no localStorage on the server, and no disk write
|
|
7
|
+
* that would be safe to do implicitly on every request) -- it only smooths
|
|
8
|
+
* over a send failure that gets retried by a *later* error in the same warm
|
|
9
|
+
* process/instance. On serverless platforms where each invocation is a
|
|
10
|
+
* fresh process, a queued report that never gets a follow-up error to
|
|
11
|
+
* piggyback on is simply lost, same as it would be if reporting weren't
|
|
12
|
+
* retried at all.
|
|
13
|
+
*/
|
|
14
|
+
class OfflineQueue {
|
|
15
|
+
constructor(maxSize = 100) {
|
|
16
|
+
this.queue = [];
|
|
17
|
+
this.isProcessing = false;
|
|
18
|
+
this.maxSize = maxSize;
|
|
19
|
+
}
|
|
20
|
+
enqueue(report) {
|
|
21
|
+
this.queue.push({ report, timestamp: Date.now(), retries: 0 });
|
|
22
|
+
if (this.queue.length > this.maxSize) {
|
|
23
|
+
this.queue.shift();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
isEmpty() {
|
|
27
|
+
return this.queue.length === 0;
|
|
28
|
+
}
|
|
29
|
+
async processQueue(sendFn) {
|
|
30
|
+
if (this.isProcessing || this.isEmpty())
|
|
31
|
+
return;
|
|
32
|
+
this.isProcessing = true;
|
|
33
|
+
try {
|
|
34
|
+
while (this.queue.length > 0) {
|
|
35
|
+
const queued = this.queue[0];
|
|
36
|
+
try {
|
|
37
|
+
await sendFn(queued.report);
|
|
38
|
+
this.queue.shift();
|
|
39
|
+
}
|
|
40
|
+
catch (_a) {
|
|
41
|
+
queued.retries++;
|
|
42
|
+
if (queued.retries >= 3) {
|
|
43
|
+
this.queue.shift();
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
this.isProcessing = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.OfflineQueue = OfflineQueue;
|
|
55
|
+
exports.default = OfflineQueue;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NextApiRequest } from 'next';
|
|
2
|
+
import { RequestContext } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a RequestContext from a Fetch API `Request` (App Router route
|
|
5
|
+
* handlers, middleware). Deliberately never reads `req.body` -- it's a
|
|
6
|
+
* stream that can only be consumed once, and doing it here would leave
|
|
7
|
+
* nothing for the actual handler to read.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fromFetchRequest(req: Request): RequestContext;
|
|
10
|
+
/**
|
|
11
|
+
* Builds a RequestContext from a Pages Router `NextApiRequest`, which --
|
|
12
|
+
* unlike the App Router's Fetch API Request -- already has its body parsed
|
|
13
|
+
* for us, so it's safe to include here.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fromApiRequest(req: NextApiRequest): RequestContext;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fromFetchRequest = fromFetchRequest;
|
|
4
|
+
exports.fromApiRequest = fromApiRequest;
|
|
5
|
+
/**
|
|
6
|
+
* Builds a RequestContext from a Fetch API `Request` (App Router route
|
|
7
|
+
* handlers, middleware). Deliberately never reads `req.body` -- it's a
|
|
8
|
+
* stream that can only be consumed once, and doing it here would leave
|
|
9
|
+
* nothing for the actual handler to read.
|
|
10
|
+
*/
|
|
11
|
+
function fromFetchRequest(req) {
|
|
12
|
+
const headers = {};
|
|
13
|
+
req.headers.forEach((value, key) => {
|
|
14
|
+
headers[key] = value;
|
|
15
|
+
});
|
|
16
|
+
const url = new URL(req.url);
|
|
17
|
+
const query = {};
|
|
18
|
+
url.searchParams.forEach((value, key) => {
|
|
19
|
+
query[key] = value;
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
url: url.pathname + url.search,
|
|
23
|
+
method: req.method,
|
|
24
|
+
headers,
|
|
25
|
+
query,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Builds a RequestContext from a Pages Router `NextApiRequest`, which --
|
|
30
|
+
* unlike the App Router's Fetch API Request -- already has its body parsed
|
|
31
|
+
* for us, so it's safe to include here.
|
|
32
|
+
*/
|
|
33
|
+
function fromApiRequest(req) {
|
|
34
|
+
return {
|
|
35
|
+
url: req.url || '',
|
|
36
|
+
method: req.method || '',
|
|
37
|
+
headers: req.headers,
|
|
38
|
+
query: req.query,
|
|
39
|
+
body: req.body,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Oluso } from './client';
|
|
2
|
+
export type RouteHandler<Ctx = any> = (req: Request, ctx: Ctx) => Response | Promise<Response>;
|
|
3
|
+
/**
|
|
4
|
+
* Wraps an App Router route handler (`app/**\/route.ts`). Reports thrown
|
|
5
|
+
* errors and 5xx responses, with a request-scoped breadcrumb trail around
|
|
6
|
+
* the handler call, then re-throws / passes the response through unchanged
|
|
7
|
+
* so Next.js's own error handling still runs.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // app/api/widgets/route.ts
|
|
11
|
+
* export const GET = withOluso(oluso, async (req) => {
|
|
12
|
+
* ...
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Each exported method (GET/POST/...) needs its own `withOluso(...)` call --
|
|
17
|
+
* Next.js dispatches by which named export exists, same reason
|
|
18
|
+
* @oluso/node's Express adapter needs two separate functions instead of one.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withOluso<Ctx = any>(oluso: Oluso, handler: RouteHandler<Ctx>): RouteHandler<Ctx>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withOluso = withOluso;
|
|
4
|
+
const request_context_1 = require("./request-context");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps an App Router route handler (`app/**\/route.ts`). Reports thrown
|
|
7
|
+
* errors and 5xx responses, with a request-scoped breadcrumb trail around
|
|
8
|
+
* the handler call, then re-throws / passes the response through unchanged
|
|
9
|
+
* so Next.js's own error handling still runs.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* // app/api/widgets/route.ts
|
|
13
|
+
* export const GET = withOluso(oluso, async (req) => {
|
|
14
|
+
* ...
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Each exported method (GET/POST/...) needs its own `withOluso(...)` call --
|
|
19
|
+
* Next.js dispatches by which named export exists, same reason
|
|
20
|
+
* @oluso/node's Express adapter needs two separate functions instead of one.
|
|
21
|
+
*/
|
|
22
|
+
function withOluso(oluso, handler) {
|
|
23
|
+
return function olusoRouteHandler(req, ctx) {
|
|
24
|
+
return oluso.runInContext(async () => {
|
|
25
|
+
const url = new URL(req.url);
|
|
26
|
+
oluso.addBreadcrumb({
|
|
27
|
+
message: `${req.method} ${url.pathname}`,
|
|
28
|
+
level: 'info',
|
|
29
|
+
category: 'http',
|
|
30
|
+
data: { method: req.method, url: url.pathname },
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const res = await handler(req, ctx);
|
|
34
|
+
if (res.status >= 500) {
|
|
35
|
+
const error = new Error(`Server error: ${res.status} - ${req.method} ${url.pathname}`);
|
|
36
|
+
error.severity = 'critical';
|
|
37
|
+
await oluso.reportError(error, (0, request_context_1.fromFetchRequest)(req));
|
|
38
|
+
}
|
|
39
|
+
return res;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
43
|
+
err.severity = 'critical';
|
|
44
|
+
await oluso.reportError(err, (0, request_context_1.fromFetchRequest)(req));
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RuntimeServerContext } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* `process.env.NEXT_RUNTIME` is Next.js's own documented way of telling
|
|
4
|
+
* `nodejs` and `edge` execution apart at runtime (it's how their own
|
|
5
|
+
* instrumentation.ts examples branch). Edge has no `os` module and no
|
|
6
|
+
* `process.memoryUsage`/`process.uptime`, so anything Node-only has to be
|
|
7
|
+
* gated behind this check rather than just feature-detected -- some Edge
|
|
8
|
+
* polyfills of `process` are convincing enough that a bare try/catch around
|
|
9
|
+
* `os.hostname()` isn't a reliable signal on its own.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRuntime(): 'nodejs' | 'edge';
|
|
12
|
+
export declare function getRuntimeServerContext(): RuntimeServerContext;
|
|
13
|
+
export default getRuntimeServerContext;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRuntime = getRuntime;
|
|
4
|
+
exports.getRuntimeServerContext = getRuntimeServerContext;
|
|
5
|
+
/**
|
|
6
|
+
* `process.env.NEXT_RUNTIME` is Next.js's own documented way of telling
|
|
7
|
+
* `nodejs` and `edge` execution apart at runtime (it's how their own
|
|
8
|
+
* instrumentation.ts examples branch). Edge has no `os` module and no
|
|
9
|
+
* `process.memoryUsage`/`process.uptime`, so anything Node-only has to be
|
|
10
|
+
* gated behind this check rather than just feature-detected -- some Edge
|
|
11
|
+
* polyfills of `process` are convincing enough that a bare try/catch around
|
|
12
|
+
* `os.hostname()` isn't a reliable signal on its own.
|
|
13
|
+
*/
|
|
14
|
+
function getRuntime() {
|
|
15
|
+
if (typeof process !== 'undefined' && process.env && process.env.NEXT_RUNTIME === 'edge') {
|
|
16
|
+
return 'edge';
|
|
17
|
+
}
|
|
18
|
+
return 'nodejs';
|
|
19
|
+
}
|
|
20
|
+
function getRuntimeServerContext() {
|
|
21
|
+
const runtime = getRuntime();
|
|
22
|
+
if (runtime === 'edge') {
|
|
23
|
+
return {
|
|
24
|
+
runtime,
|
|
25
|
+
nodeVersion: typeof process !== 'undefined' ? process.version : undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const os = require('os');
|
|
30
|
+
const memUsage = process.memoryUsage();
|
|
31
|
+
return {
|
|
32
|
+
runtime,
|
|
33
|
+
hostname: os.hostname(),
|
|
34
|
+
platform: `${os.platform()} ${os.release()}`,
|
|
35
|
+
nodeVersion: process.version,
|
|
36
|
+
memory: {
|
|
37
|
+
used: memUsage.heapUsed,
|
|
38
|
+
total: memUsage.heapTotal,
|
|
39
|
+
},
|
|
40
|
+
uptime: process.uptime(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
return { runtime };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.default = getRuntimeServerContext;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ErrorReport } from '@oluso/core';
|
|
2
|
+
interface SendOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Send an error report via fetch. Works unchanged on both the Node.js and
|
|
8
|
+
* Edge runtimes Next.js supports (unlike @oluso/node's transport, which
|
|
9
|
+
* uses Node's http/https modules and would break on Edge). Never rejects
|
|
10
|
+
* with an unhandled path the caller has to catch differently -- a failed
|
|
11
|
+
* send just means the report gets queued.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sendErrorReport(reportUrl: string, errorReport: ErrorReport, options: SendOptions): Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendErrorReport = sendErrorReport;
|
|
4
|
+
/**
|
|
5
|
+
* Send an error report via fetch. Works unchanged on both the Node.js and
|
|
6
|
+
* Edge runtimes Next.js supports (unlike @oluso/node's transport, which
|
|
7
|
+
* uses Node's http/https modules and would break on Edge). Never rejects
|
|
8
|
+
* with an unhandled path the caller has to catch differently -- a failed
|
|
9
|
+
* send just means the report gets queued.
|
|
10
|
+
*/
|
|
11
|
+
function sendErrorReport(reportUrl, errorReport, options) {
|
|
12
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;
|
|
13
|
+
const timeoutId = controller
|
|
14
|
+
? setTimeout(() => controller.abort(), options.timeout || 5000)
|
|
15
|
+
: undefined;
|
|
16
|
+
return fetch(reportUrl, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
'x-oluso-signature': options.apiKey,
|
|
21
|
+
},
|
|
22
|
+
body: JSON.stringify(errorReport),
|
|
23
|
+
signal: controller === null || controller === void 0 ? void 0 : controller.signal,
|
|
24
|
+
})
|
|
25
|
+
.then((res) => {
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
console.error(`[Oluso] Error reporting failed with status ${res.status}`);
|
|
28
|
+
throw new Error(`Oluso reporting failed with status ${res.status}`);
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
.catch((err) => {
|
|
32
|
+
console.error('[Oluso] Failed to send error report:', (err === null || err === void 0 ? void 0 : err.message) || err);
|
|
33
|
+
throw err;
|
|
34
|
+
})
|
|
35
|
+
.finally(() => {
|
|
36
|
+
if (timeoutId)
|
|
37
|
+
clearTimeout(timeoutId);
|
|
38
|
+
});
|
|
39
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseOlusoOptions } from '@oluso/core';
|
|
2
|
+
export interface OlusoNextjsOptions extends BaseOlusoOptions {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Request info attached to an error report. Deliberately narrower than the
|
|
6
|
+
* Node adapter's RequestContext -- the App Router's `Request` body is a
|
|
7
|
+
* stream that can only be read once, and reading it here would leave
|
|
8
|
+
* nothing for the actual route handler to consume, so `body` is only ever
|
|
9
|
+
* populated from the Pages Router (`NextApiRequest` already parses it).
|
|
10
|
+
*/
|
|
11
|
+
export interface RequestContext {
|
|
12
|
+
url: string;
|
|
13
|
+
method: string;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
query?: Record<string, any>;
|
|
16
|
+
body?: any;
|
|
17
|
+
/** Which Next.js surface the request came through, when known. */
|
|
18
|
+
routeType?: 'route-handler' | 'api-route' | 'middleware' | 'render' | 'action';
|
|
19
|
+
}
|
|
20
|
+
export interface RuntimeServerContext {
|
|
21
|
+
runtime: 'nodejs' | 'edge';
|
|
22
|
+
hostname?: string;
|
|
23
|
+
platform?: string;
|
|
24
|
+
nodeVersion?: string;
|
|
25
|
+
memory?: {
|
|
26
|
+
used: number;
|
|
27
|
+
total: number;
|
|
28
|
+
};
|
|
29
|
+
uptime?: number;
|
|
30
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports the error Next.js hands to an `error.tsx` boundary. Meant to be
|
|
3
|
+
* called from the top of that file:
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* 'use client';
|
|
7
|
+
* import { useOlusoErrorEffect } from '@oluso/nextjs/client';
|
|
8
|
+
*
|
|
9
|
+
* export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
10
|
+
* useOlusoErrorEffect(error);
|
|
11
|
+
* return <div>Something went wrong <button onClick={reset}>Try again</button></div>;
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Must be used within an `<OlusoProvider>` higher in the tree -- which
|
|
16
|
+
* rules out `global-error.tsx` specifically, since it replaces the root
|
|
17
|
+
* layout that the provider would normally live in. For that file, construct
|
|
18
|
+
* an `OlusoClient` directly instead of using this hook.
|
|
19
|
+
*
|
|
20
|
+
* Reports in a `useEffect` (not during render) since `error.tsx` re-renders
|
|
21
|
+
* on every `reset()` attempt with the same error object until it's actually
|
|
22
|
+
* resolved -- reporting during render would re-report on each retry render,
|
|
23
|
+
* not just the first.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useOlusoErrorEffect(error: Error & {
|
|
26
|
+
digest?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
export default useOlusoErrorEffect;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useOlusoErrorEffect = useOlusoErrorEffect;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_2 = require("@oluso/react");
|
|
7
|
+
/**
|
|
8
|
+
* Reports the error Next.js hands to an `error.tsx` boundary. Meant to be
|
|
9
|
+
* called from the top of that file:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* 'use client';
|
|
13
|
+
* import { useOlusoErrorEffect } from '@oluso/nextjs/client';
|
|
14
|
+
*
|
|
15
|
+
* export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
16
|
+
* useOlusoErrorEffect(error);
|
|
17
|
+
* return <div>Something went wrong <button onClick={reset}>Try again</button></div>;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Must be used within an `<OlusoProvider>` higher in the tree -- which
|
|
22
|
+
* rules out `global-error.tsx` specifically, since it replaces the root
|
|
23
|
+
* layout that the provider would normally live in. For that file, construct
|
|
24
|
+
* an `OlusoClient` directly instead of using this hook.
|
|
25
|
+
*
|
|
26
|
+
* Reports in a `useEffect` (not during render) since `error.tsx` re-renders
|
|
27
|
+
* on every `reset()` attempt with the same error object until it's actually
|
|
28
|
+
* resolved -- reporting during render would re-report on each retry render,
|
|
29
|
+
* not just the first.
|
|
30
|
+
*/
|
|
31
|
+
function useOlusoErrorEffect(error) {
|
|
32
|
+
const client = (0, react_2.useOluso)();
|
|
33
|
+
const reportedRef = (0, react_1.useRef)(null);
|
|
34
|
+
(0, react_1.useEffect)(() => {
|
|
35
|
+
if (reportedRef.current === error)
|
|
36
|
+
return;
|
|
37
|
+
reportedRef.current = error;
|
|
38
|
+
client.captureException(error, error.digest ? { digest: error.digest } : undefined);
|
|
39
|
+
}, [error, client]);
|
|
40
|
+
}
|
|
41
|
+
exports.default = useOlusoErrorEffect;
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oluso/nextjs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Oluso error monitoring for Next.js applications: instrumentation hooks, route/middleware wrappers, and React bindings for the App Router and Pages Router",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./client": {
|
|
13
|
+
"types": "./dist/client.d.ts",
|
|
14
|
+
"default": "./dist/client.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "jest"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"error",
|
|
24
|
+
"monitoring",
|
|
25
|
+
"reporting",
|
|
26
|
+
"nextjs",
|
|
27
|
+
"next",
|
|
28
|
+
"react",
|
|
29
|
+
"app-router",
|
|
30
|
+
"edge",
|
|
31
|
+
"error-tracking",
|
|
32
|
+
"error-monitoring",
|
|
33
|
+
"ai",
|
|
34
|
+
"crash-reporting",
|
|
35
|
+
"logging",
|
|
36
|
+
"debugging"
|
|
37
|
+
],
|
|
38
|
+
"author": "David Ezeh",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/olusodotdev/oluso-js.git",
|
|
43
|
+
"directory": "packages/nextjs"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/olusodotdev/oluso-js/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/olusodotdev/oluso-js/tree/main/packages/nextjs#readme",
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@oluso/core": "1.0.0",
|
|
56
|
+
"@oluso/react": "1.1.0",
|
|
57
|
+
"tslib": "^2.8.1"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"next": ">=13.4.0",
|
|
61
|
+
"react": ">=18.2.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@testing-library/react": "^16.0.0",
|
|
65
|
+
"@types/jest": "^30.0.0",
|
|
66
|
+
"@types/node": "^18.15.11",
|
|
67
|
+
"@types/react": "^18.2.0",
|
|
68
|
+
"jest": "^29.5.0",
|
|
69
|
+
"jest-environment-jsdom": "^29.5.0",
|
|
70
|
+
"next": "^14.2.0",
|
|
71
|
+
"react": "^18.2.0",
|
|
72
|
+
"react-dom": "^18.2.0",
|
|
73
|
+
"ts-jest": "^29.1.0",
|
|
74
|
+
"typescript": "^5.0.4"
|
|
75
|
+
}
|
|
76
|
+
}
|