@nexttrace/next 0.1.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/README.md +78 -0
- package/dist/chunk-7FTNN4TD.mjs +191 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +83 -0
- package/dist/index.mjs +60 -0
- package/dist/server.d.mts +15 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +209 -0
- package/dist/server.mjs +190 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @nexttrace/next
|
|
2
|
+
|
|
3
|
+
> Next.js integration for **NextTrace** — the local-first, privacy-respecting API debugging overlay and developer observability toolkit.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ⚡ **Zero External Telemetry**: All events remain in local browser/server memory.
|
|
8
|
+
- 🔒 **Deep Secret Redaction**: Automatically redacts Bearer tokens, API keys, passwords, and custom sensitive fields.
|
|
9
|
+
- 🎯 **App Router Native**: Seamlessly captures client-side `fetch` and outgoing server-side `fetch` via Next.js instrumentation.
|
|
10
|
+
- 🛠️ **Dev-Only Gating**: Tree-shaken in production; renders `null` unless `NODE_ENV === 'development'`.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# npm
|
|
16
|
+
npm install --save-dev @nexttrace/next @nexttrace/core
|
|
17
|
+
|
|
18
|
+
# pnpm
|
|
19
|
+
pnpm add -D @nexttrace/next @nexttrace/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Mount the Client Overlay
|
|
25
|
+
|
|
26
|
+
Add `<NextTraceOverlay />` to your root layout (`app/layout.tsx`):
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { NextTraceOverlay } from '@nexttrace/next';
|
|
30
|
+
|
|
31
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
32
|
+
return (
|
|
33
|
+
<html lang="en">
|
|
34
|
+
<body>
|
|
35
|
+
{children}
|
|
36
|
+
<NextTraceOverlay defaultPosition="bottom" />
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. (Optional) Trace Server-Side `fetch`
|
|
44
|
+
|
|
45
|
+
To capture outgoing `fetch` calls made inside Server Components, Server Actions, or Route Handlers:
|
|
46
|
+
|
|
47
|
+
1. Enable the instrumentation hook in `next.config.js`:
|
|
48
|
+
```js
|
|
49
|
+
/** @type {import('next').NextConfig} */
|
|
50
|
+
const nextConfig = {
|
|
51
|
+
experimental: {
|
|
52
|
+
instrumentationHook: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
module.exports = nextConfig;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. Register server tracing in `instrumentation.ts` in your project root:
|
|
60
|
+
```ts
|
|
61
|
+
export async function register() {
|
|
62
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
63
|
+
const { traceNextServer } = await import('@nexttrace/next/server');
|
|
64
|
+
traceNextServer();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3. Expose the dev-only collector route at `app/api/__nexttrace/route.ts`:
|
|
70
|
+
```ts
|
|
71
|
+
import { createNextTraceRouteHandler } from '@nexttrace/next/server';
|
|
72
|
+
|
|
73
|
+
export const { GET, POST } = createNextTraceRouteHandler();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT © NextTrace Team
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import {
|
|
3
|
+
redactHeaders,
|
|
4
|
+
sanitizeUrlAndQuery,
|
|
5
|
+
sanitizeBodyPreview,
|
|
6
|
+
DEFAULT_DENY_HEADERS,
|
|
7
|
+
DEFAULT_DENY_QUERY_PARAMS,
|
|
8
|
+
DEFAULT_DENY_JSON_KEYS
|
|
9
|
+
} from "@nexttrace/core";
|
|
10
|
+
var serverEventQueue = [];
|
|
11
|
+
var MAX_SERVER_EVENTS = 100;
|
|
12
|
+
var isServerInstrumented = false;
|
|
13
|
+
var originalGlobalFetch = null;
|
|
14
|
+
function generateEventId() {
|
|
15
|
+
return "srv_" + Math.random().toString(36).substring(2, 10) + Date.now().toString(36);
|
|
16
|
+
}
|
|
17
|
+
function instrumentServerFetch() {
|
|
18
|
+
if (process.env.NODE_ENV !== "development") {
|
|
19
|
+
return () => {
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (isServerInstrumented || typeof globalThis.fetch === "undefined") {
|
|
23
|
+
return () => {
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
originalGlobalFetch = globalThis.fetch;
|
|
27
|
+
isServerInstrumented = true;
|
|
28
|
+
globalThis.fetch = async function(input, init) {
|
|
29
|
+
if (!originalGlobalFetch) return fetch(input, init);
|
|
30
|
+
const urlString = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
31
|
+
if (urlString.includes("/__nexttrace") || urlString.includes("/_next/")) {
|
|
32
|
+
return originalGlobalFetch.call(globalThis, input, init);
|
|
33
|
+
}
|
|
34
|
+
const eventId = generateEventId();
|
|
35
|
+
const startTime = performance.now();
|
|
36
|
+
const timestamp = Date.now();
|
|
37
|
+
let method = "GET";
|
|
38
|
+
const rawHeaders = {};
|
|
39
|
+
let reqBodyText = null;
|
|
40
|
+
if (init) {
|
|
41
|
+
if (init.method) method = init.method.toUpperCase();
|
|
42
|
+
if (init.headers) {
|
|
43
|
+
if (init.headers instanceof Headers) {
|
|
44
|
+
init.headers.forEach((v, k) => {
|
|
45
|
+
rawHeaders[k] = v;
|
|
46
|
+
});
|
|
47
|
+
} else if (Array.isArray(init.headers)) {
|
|
48
|
+
init.headers.forEach(([k, v]) => {
|
|
49
|
+
rawHeaders[k] = v;
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
Object.entries(init.headers).forEach(([k, v]) => {
|
|
53
|
+
rawHeaders[k] = String(v);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (init.body && typeof init.body === "string") {
|
|
58
|
+
reqBodyText = init.body;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const { sanitizedUrl, queryParams, wasRedacted: queryRedacted } = sanitizeUrlAndQuery(
|
|
62
|
+
urlString,
|
|
63
|
+
DEFAULT_DENY_QUERY_PARAMS
|
|
64
|
+
);
|
|
65
|
+
const { sanitized: sanitizedReqHeaders, wasRedacted: headersRedacted } = redactHeaders(
|
|
66
|
+
rawHeaders,
|
|
67
|
+
DEFAULT_DENY_HEADERS
|
|
68
|
+
);
|
|
69
|
+
const { preview: reqBodyPreview, wasRedacted: bodyRedacted } = sanitizeBodyPreview(
|
|
70
|
+
reqBodyText,
|
|
71
|
+
25e3,
|
|
72
|
+
DEFAULT_DENY_JSON_KEYS
|
|
73
|
+
);
|
|
74
|
+
const event = {
|
|
75
|
+
schemaVersion: "1.0.0",
|
|
76
|
+
eventId,
|
|
77
|
+
timestamp,
|
|
78
|
+
phase: "start",
|
|
79
|
+
runtime: "node",
|
|
80
|
+
sourceAdapter: "server-fetch",
|
|
81
|
+
request: {
|
|
82
|
+
method,
|
|
83
|
+
url: urlString,
|
|
84
|
+
sanitizedUrl,
|
|
85
|
+
queryParams,
|
|
86
|
+
headers: sanitizedReqHeaders,
|
|
87
|
+
bodyPreview: reqBodyPreview,
|
|
88
|
+
bodySize: reqBodyText ? reqBodyText.length : 0,
|
|
89
|
+
isTruncated: false
|
|
90
|
+
},
|
|
91
|
+
timing: {
|
|
92
|
+
startTime
|
|
93
|
+
},
|
|
94
|
+
context: {
|
|
95
|
+
attributionMethod: "stack-derived",
|
|
96
|
+
confidence: "medium"
|
|
97
|
+
},
|
|
98
|
+
flags: {
|
|
99
|
+
isInternal: false,
|
|
100
|
+
isRedacted: queryRedacted || headersRedacted || bodyRedacted,
|
|
101
|
+
isTruncated: false,
|
|
102
|
+
isInferred: false
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
serverEventQueue.push(event);
|
|
106
|
+
if (serverEventQueue.length > MAX_SERVER_EVENTS) {
|
|
107
|
+
serverEventQueue.shift();
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const response = await originalGlobalFetch.call(globalThis, input, init);
|
|
111
|
+
const endTime = performance.now();
|
|
112
|
+
const duration = Math.round(endTime - startTime);
|
|
113
|
+
const resHeaders = {};
|
|
114
|
+
response.headers.forEach((v, k) => {
|
|
115
|
+
resHeaders[k] = v;
|
|
116
|
+
});
|
|
117
|
+
const { sanitized: sanitizedResHeaders, wasRedacted: resHeadersRedacted } = redactHeaders(
|
|
118
|
+
resHeaders,
|
|
119
|
+
DEFAULT_DENY_HEADERS
|
|
120
|
+
);
|
|
121
|
+
const contentType = response.headers.get("content-type") || "";
|
|
122
|
+
let resBodyPreview = null;
|
|
123
|
+
if (contentType.includes("application/json") || contentType.includes("text/")) {
|
|
124
|
+
try {
|
|
125
|
+
const clone = response.clone();
|
|
126
|
+
const raw = await clone.text();
|
|
127
|
+
const sanitized = sanitizeBodyPreview(raw, 25e3, DEFAULT_DENY_JSON_KEYS);
|
|
128
|
+
resBodyPreview = sanitized.preview;
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
event.phase = response.ok ? "complete" : "error";
|
|
133
|
+
event.timing.endTime = endTime;
|
|
134
|
+
event.timing.duration = duration;
|
|
135
|
+
event.flags.isSlow = duration >= 1e3;
|
|
136
|
+
event.flags.isRedacted = event.flags.isRedacted || resHeadersRedacted;
|
|
137
|
+
event.response = {
|
|
138
|
+
status: response.status,
|
|
139
|
+
statusText: response.statusText,
|
|
140
|
+
headers: sanitizedResHeaders,
|
|
141
|
+
bodyPreview: resBodyPreview,
|
|
142
|
+
contentType,
|
|
143
|
+
bodySize: resBodyPreview ? resBodyPreview.length : 0,
|
|
144
|
+
isTruncated: false
|
|
145
|
+
};
|
|
146
|
+
return response;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
const endTime = performance.now();
|
|
149
|
+
event.phase = "error";
|
|
150
|
+
event.timing.endTime = endTime;
|
|
151
|
+
event.timing.duration = Math.round(endTime - startTime);
|
|
152
|
+
event.error = {
|
|
153
|
+
name: err instanceof Error ? err.name : "ServerError",
|
|
154
|
+
message: err instanceof Error ? err.message : String(err)
|
|
155
|
+
};
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
return () => {
|
|
160
|
+
if (isServerInstrumented && originalGlobalFetch) {
|
|
161
|
+
globalThis.fetch = originalGlobalFetch;
|
|
162
|
+
originalGlobalFetch = null;
|
|
163
|
+
isServerInstrumented = false;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function createNextTraceRouteHandler() {
|
|
168
|
+
return async function GET() {
|
|
169
|
+
if (process.env.NODE_ENV !== "development") {
|
|
170
|
+
return new Response(JSON.stringify({ error: "Forbidden in production" }), {
|
|
171
|
+
status: 403,
|
|
172
|
+
headers: { "Content-Type": "application/json" }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const events = [...serverEventQueue];
|
|
176
|
+
serverEventQueue.length = 0;
|
|
177
|
+
return new Response(JSON.stringify({ events }), {
|
|
178
|
+
status: 200,
|
|
179
|
+
headers: {
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
"Cache-Control": "no-store, no-cache",
|
|
182
|
+
"x-nexttrace-internal": "true"
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
instrumentServerFetch,
|
|
190
|
+
createNextTraceRouteHandler
|
|
191
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NextTraceStore } from '@nexttrace/core';
|
|
3
|
+
import { DockPosition, DevToolsTheme } from '@nexttrace/devtools';
|
|
4
|
+
|
|
5
|
+
interface NextTraceOverlayProps {
|
|
6
|
+
store?: NextTraceStore;
|
|
7
|
+
defaultPosition?: DockPosition;
|
|
8
|
+
defaultTheme?: DevToolsTheme;
|
|
9
|
+
initialOpen?: boolean;
|
|
10
|
+
enableServerSync?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const NextTraceOverlay: React.FC<NextTraceOverlayProps>;
|
|
13
|
+
|
|
14
|
+
export { NextTraceOverlay, type NextTraceOverlayProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NextTraceStore } from '@nexttrace/core';
|
|
3
|
+
import { DockPosition, DevToolsTheme } from '@nexttrace/devtools';
|
|
4
|
+
|
|
5
|
+
interface NextTraceOverlayProps {
|
|
6
|
+
store?: NextTraceStore;
|
|
7
|
+
defaultPosition?: DockPosition;
|
|
8
|
+
defaultTheme?: DevToolsTheme;
|
|
9
|
+
initialOpen?: boolean;
|
|
10
|
+
enableServerSync?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const NextTraceOverlay: React.FC<NextTraceOverlayProps>;
|
|
13
|
+
|
|
14
|
+
export { NextTraceOverlay, type NextTraceOverlayProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
NextTraceOverlay: () => NextTraceOverlay
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/client.tsx
|
|
29
|
+
var import_react = require("react");
|
|
30
|
+
var import_core = require("@nexttrace/core");
|
|
31
|
+
var import_devtools = require("@nexttrace/devtools");
|
|
32
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
|
+
var NextTraceOverlay = ({
|
|
34
|
+
store: propStore,
|
|
35
|
+
defaultPosition = "bottom",
|
|
36
|
+
defaultTheme = "dark",
|
|
37
|
+
initialOpen = false,
|
|
38
|
+
enableServerSync = true
|
|
39
|
+
}) => {
|
|
40
|
+
if (process.env.NODE_ENV !== "development") {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const store = propStore || (0, import_core.getGlobalStore)();
|
|
44
|
+
(0, import_react.useEffect)(() => {
|
|
45
|
+
const uninstallFetch = (0, import_core.installFetchInterceptor)(store);
|
|
46
|
+
let syncInterval = null;
|
|
47
|
+
if (enableServerSync) {
|
|
48
|
+
syncInterval = setInterval(async () => {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch("/api/__nexttrace", {
|
|
51
|
+
headers: { "x-nexttrace-internal": "true" }
|
|
52
|
+
});
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
const data = await res.json();
|
|
55
|
+
if (data.events && Array.isArray(data.events)) {
|
|
56
|
+
data.events.forEach((srvEvent) => {
|
|
57
|
+
store.recordEvent(srvEvent);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}, 2e3);
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
uninstallFetch();
|
|
67
|
+
if (syncInterval) clearInterval(syncInterval);
|
|
68
|
+
};
|
|
69
|
+
}, [store, enableServerSync]);
|
|
70
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
71
|
+
import_devtools.NextTraceDevTools,
|
|
72
|
+
{
|
|
73
|
+
store,
|
|
74
|
+
defaultPosition,
|
|
75
|
+
defaultTheme,
|
|
76
|
+
initialOpen
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
81
|
+
0 && (module.exports = {
|
|
82
|
+
NextTraceOverlay
|
|
83
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/client.tsx
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import {
|
|
6
|
+
installFetchInterceptor,
|
|
7
|
+
getGlobalStore
|
|
8
|
+
} from "@nexttrace/core";
|
|
9
|
+
import { NextTraceDevTools } from "@nexttrace/devtools";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
var NextTraceOverlay = ({
|
|
12
|
+
store: propStore,
|
|
13
|
+
defaultPosition = "bottom",
|
|
14
|
+
defaultTheme = "dark",
|
|
15
|
+
initialOpen = false,
|
|
16
|
+
enableServerSync = true
|
|
17
|
+
}) => {
|
|
18
|
+
if (process.env.NODE_ENV !== "development") {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const store = propStore || getGlobalStore();
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const uninstallFetch = installFetchInterceptor(store);
|
|
24
|
+
let syncInterval = null;
|
|
25
|
+
if (enableServerSync) {
|
|
26
|
+
syncInterval = setInterval(async () => {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch("/api/__nexttrace", {
|
|
29
|
+
headers: { "x-nexttrace-internal": "true" }
|
|
30
|
+
});
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
if (data.events && Array.isArray(data.events)) {
|
|
34
|
+
data.events.forEach((srvEvent) => {
|
|
35
|
+
store.recordEvent(srvEvent);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}, 2e3);
|
|
42
|
+
}
|
|
43
|
+
return () => {
|
|
44
|
+
uninstallFetch();
|
|
45
|
+
if (syncInterval) clearInterval(syncInterval);
|
|
46
|
+
};
|
|
47
|
+
}, [store, enableServerSync]);
|
|
48
|
+
return /* @__PURE__ */ jsx(
|
|
49
|
+
NextTraceDevTools,
|
|
50
|
+
{
|
|
51
|
+
store,
|
|
52
|
+
defaultPosition,
|
|
53
|
+
defaultTheme,
|
|
54
|
+
initialOpen
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
export {
|
|
59
|
+
NextTraceOverlay
|
|
60
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NextTrace Server-Side Outgoing Fetch Collector & Route Handler
|
|
3
|
+
* Captures outgoing HTTP calls made in Server Components, Route Handlers, or Server Actions.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Instruments globalThis.fetch on the Node.js server side in development
|
|
7
|
+
*/
|
|
8
|
+
declare function instrumentServerFetch(): () => void;
|
|
9
|
+
/**
|
|
10
|
+
* Next.js App Router Route Handler for `app/api/__nexttrace/route.ts`
|
|
11
|
+
* Exposes server events in dev mode only.
|
|
12
|
+
*/
|
|
13
|
+
declare function createNextTraceRouteHandler(): () => Promise<Response>;
|
|
14
|
+
|
|
15
|
+
export { createNextTraceRouteHandler, instrumentServerFetch };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NextTrace Server-Side Outgoing Fetch Collector & Route Handler
|
|
3
|
+
* Captures outgoing HTTP calls made in Server Components, Route Handlers, or Server Actions.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Instruments globalThis.fetch on the Node.js server side in development
|
|
7
|
+
*/
|
|
8
|
+
declare function instrumentServerFetch(): () => void;
|
|
9
|
+
/**
|
|
10
|
+
* Next.js App Router Route Handler for `app/api/__nexttrace/route.ts`
|
|
11
|
+
* Exposes server events in dev mode only.
|
|
12
|
+
*/
|
|
13
|
+
declare function createNextTraceRouteHandler(): () => Promise<Response>;
|
|
14
|
+
|
|
15
|
+
export { createNextTraceRouteHandler, instrumentServerFetch };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
createNextTraceRouteHandler: () => createNextTraceRouteHandler,
|
|
24
|
+
instrumentServerFetch: () => instrumentServerFetch
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(server_exports);
|
|
27
|
+
var import_core = require("@nexttrace/core");
|
|
28
|
+
var serverEventQueue = [];
|
|
29
|
+
var MAX_SERVER_EVENTS = 100;
|
|
30
|
+
var isServerInstrumented = false;
|
|
31
|
+
var originalGlobalFetch = null;
|
|
32
|
+
function generateEventId() {
|
|
33
|
+
return "srv_" + Math.random().toString(36).substring(2, 10) + Date.now().toString(36);
|
|
34
|
+
}
|
|
35
|
+
function instrumentServerFetch() {
|
|
36
|
+
if (process.env.NODE_ENV !== "development") {
|
|
37
|
+
return () => {
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (isServerInstrumented || typeof globalThis.fetch === "undefined") {
|
|
41
|
+
return () => {
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
originalGlobalFetch = globalThis.fetch;
|
|
45
|
+
isServerInstrumented = true;
|
|
46
|
+
globalThis.fetch = async function(input, init) {
|
|
47
|
+
if (!originalGlobalFetch) return fetch(input, init);
|
|
48
|
+
const urlString = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
49
|
+
if (urlString.includes("/__nexttrace") || urlString.includes("/_next/")) {
|
|
50
|
+
return originalGlobalFetch.call(globalThis, input, init);
|
|
51
|
+
}
|
|
52
|
+
const eventId = generateEventId();
|
|
53
|
+
const startTime = performance.now();
|
|
54
|
+
const timestamp = Date.now();
|
|
55
|
+
let method = "GET";
|
|
56
|
+
const rawHeaders = {};
|
|
57
|
+
let reqBodyText = null;
|
|
58
|
+
if (init) {
|
|
59
|
+
if (init.method) method = init.method.toUpperCase();
|
|
60
|
+
if (init.headers) {
|
|
61
|
+
if (init.headers instanceof Headers) {
|
|
62
|
+
init.headers.forEach((v, k) => {
|
|
63
|
+
rawHeaders[k] = v;
|
|
64
|
+
});
|
|
65
|
+
} else if (Array.isArray(init.headers)) {
|
|
66
|
+
init.headers.forEach(([k, v]) => {
|
|
67
|
+
rawHeaders[k] = v;
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
Object.entries(init.headers).forEach(([k, v]) => {
|
|
71
|
+
rawHeaders[k] = String(v);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (init.body && typeof init.body === "string") {
|
|
76
|
+
reqBodyText = init.body;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const { sanitizedUrl, queryParams, wasRedacted: queryRedacted } = (0, import_core.sanitizeUrlAndQuery)(
|
|
80
|
+
urlString,
|
|
81
|
+
import_core.DEFAULT_DENY_QUERY_PARAMS
|
|
82
|
+
);
|
|
83
|
+
const { sanitized: sanitizedReqHeaders, wasRedacted: headersRedacted } = (0, import_core.redactHeaders)(
|
|
84
|
+
rawHeaders,
|
|
85
|
+
import_core.DEFAULT_DENY_HEADERS
|
|
86
|
+
);
|
|
87
|
+
const { preview: reqBodyPreview, wasRedacted: bodyRedacted } = (0, import_core.sanitizeBodyPreview)(
|
|
88
|
+
reqBodyText,
|
|
89
|
+
25e3,
|
|
90
|
+
import_core.DEFAULT_DENY_JSON_KEYS
|
|
91
|
+
);
|
|
92
|
+
const event = {
|
|
93
|
+
schemaVersion: "1.0.0",
|
|
94
|
+
eventId,
|
|
95
|
+
timestamp,
|
|
96
|
+
phase: "start",
|
|
97
|
+
runtime: "node",
|
|
98
|
+
sourceAdapter: "server-fetch",
|
|
99
|
+
request: {
|
|
100
|
+
method,
|
|
101
|
+
url: urlString,
|
|
102
|
+
sanitizedUrl,
|
|
103
|
+
queryParams,
|
|
104
|
+
headers: sanitizedReqHeaders,
|
|
105
|
+
bodyPreview: reqBodyPreview,
|
|
106
|
+
bodySize: reqBodyText ? reqBodyText.length : 0,
|
|
107
|
+
isTruncated: false
|
|
108
|
+
},
|
|
109
|
+
timing: {
|
|
110
|
+
startTime
|
|
111
|
+
},
|
|
112
|
+
context: {
|
|
113
|
+
attributionMethod: "stack-derived",
|
|
114
|
+
confidence: "medium"
|
|
115
|
+
},
|
|
116
|
+
flags: {
|
|
117
|
+
isInternal: false,
|
|
118
|
+
isRedacted: queryRedacted || headersRedacted || bodyRedacted,
|
|
119
|
+
isTruncated: false,
|
|
120
|
+
isInferred: false
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
serverEventQueue.push(event);
|
|
124
|
+
if (serverEventQueue.length > MAX_SERVER_EVENTS) {
|
|
125
|
+
serverEventQueue.shift();
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const response = await originalGlobalFetch.call(globalThis, input, init);
|
|
129
|
+
const endTime = performance.now();
|
|
130
|
+
const duration = Math.round(endTime - startTime);
|
|
131
|
+
const resHeaders = {};
|
|
132
|
+
response.headers.forEach((v, k) => {
|
|
133
|
+
resHeaders[k] = v;
|
|
134
|
+
});
|
|
135
|
+
const { sanitized: sanitizedResHeaders, wasRedacted: resHeadersRedacted } = (0, import_core.redactHeaders)(
|
|
136
|
+
resHeaders,
|
|
137
|
+
import_core.DEFAULT_DENY_HEADERS
|
|
138
|
+
);
|
|
139
|
+
const contentType = response.headers.get("content-type") || "";
|
|
140
|
+
let resBodyPreview = null;
|
|
141
|
+
if (contentType.includes("application/json") || contentType.includes("text/")) {
|
|
142
|
+
try {
|
|
143
|
+
const clone = response.clone();
|
|
144
|
+
const raw = await clone.text();
|
|
145
|
+
const sanitized = (0, import_core.sanitizeBodyPreview)(raw, 25e3, import_core.DEFAULT_DENY_JSON_KEYS);
|
|
146
|
+
resBodyPreview = sanitized.preview;
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
event.phase = response.ok ? "complete" : "error";
|
|
151
|
+
event.timing.endTime = endTime;
|
|
152
|
+
event.timing.duration = duration;
|
|
153
|
+
event.flags.isSlow = duration >= 1e3;
|
|
154
|
+
event.flags.isRedacted = event.flags.isRedacted || resHeadersRedacted;
|
|
155
|
+
event.response = {
|
|
156
|
+
status: response.status,
|
|
157
|
+
statusText: response.statusText,
|
|
158
|
+
headers: sanitizedResHeaders,
|
|
159
|
+
bodyPreview: resBodyPreview,
|
|
160
|
+
contentType,
|
|
161
|
+
bodySize: resBodyPreview ? resBodyPreview.length : 0,
|
|
162
|
+
isTruncated: false
|
|
163
|
+
};
|
|
164
|
+
return response;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const endTime = performance.now();
|
|
167
|
+
event.phase = "error";
|
|
168
|
+
event.timing.endTime = endTime;
|
|
169
|
+
event.timing.duration = Math.round(endTime - startTime);
|
|
170
|
+
event.error = {
|
|
171
|
+
name: err instanceof Error ? err.name : "ServerError",
|
|
172
|
+
message: err instanceof Error ? err.message : String(err)
|
|
173
|
+
};
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
return () => {
|
|
178
|
+
if (isServerInstrumented && originalGlobalFetch) {
|
|
179
|
+
globalThis.fetch = originalGlobalFetch;
|
|
180
|
+
originalGlobalFetch = null;
|
|
181
|
+
isServerInstrumented = false;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function createNextTraceRouteHandler() {
|
|
186
|
+
return async function GET() {
|
|
187
|
+
if (process.env.NODE_ENV !== "development") {
|
|
188
|
+
return new Response(JSON.stringify({ error: "Forbidden in production" }), {
|
|
189
|
+
status: 403,
|
|
190
|
+
headers: { "Content-Type": "application/json" }
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const events = [...serverEventQueue];
|
|
194
|
+
serverEventQueue.length = 0;
|
|
195
|
+
return new Response(JSON.stringify({ events }), {
|
|
196
|
+
status: 200,
|
|
197
|
+
headers: {
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
"Cache-Control": "no-store, no-cache",
|
|
200
|
+
"x-nexttrace-internal": "true"
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
206
|
+
0 && (module.exports = {
|
|
207
|
+
createNextTraceRouteHandler,
|
|
208
|
+
instrumentServerFetch
|
|
209
|
+
});
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import {
|
|
3
|
+
redactHeaders,
|
|
4
|
+
sanitizeUrlAndQuery,
|
|
5
|
+
sanitizeBodyPreview,
|
|
6
|
+
DEFAULT_DENY_HEADERS,
|
|
7
|
+
DEFAULT_DENY_QUERY_PARAMS,
|
|
8
|
+
DEFAULT_DENY_JSON_KEYS
|
|
9
|
+
} from "@nexttrace/core";
|
|
10
|
+
var serverEventQueue = [];
|
|
11
|
+
var MAX_SERVER_EVENTS = 100;
|
|
12
|
+
var isServerInstrumented = false;
|
|
13
|
+
var originalGlobalFetch = null;
|
|
14
|
+
function generateEventId() {
|
|
15
|
+
return "srv_" + Math.random().toString(36).substring(2, 10) + Date.now().toString(36);
|
|
16
|
+
}
|
|
17
|
+
function instrumentServerFetch() {
|
|
18
|
+
if (process.env.NODE_ENV !== "development") {
|
|
19
|
+
return () => {
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (isServerInstrumented || typeof globalThis.fetch === "undefined") {
|
|
23
|
+
return () => {
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
originalGlobalFetch = globalThis.fetch;
|
|
27
|
+
isServerInstrumented = true;
|
|
28
|
+
globalThis.fetch = async function(input, init) {
|
|
29
|
+
if (!originalGlobalFetch) return fetch(input, init);
|
|
30
|
+
const urlString = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
31
|
+
if (urlString.includes("/__nexttrace") || urlString.includes("/_next/")) {
|
|
32
|
+
return originalGlobalFetch.call(globalThis, input, init);
|
|
33
|
+
}
|
|
34
|
+
const eventId = generateEventId();
|
|
35
|
+
const startTime = performance.now();
|
|
36
|
+
const timestamp = Date.now();
|
|
37
|
+
let method = "GET";
|
|
38
|
+
const rawHeaders = {};
|
|
39
|
+
let reqBodyText = null;
|
|
40
|
+
if (init) {
|
|
41
|
+
if (init.method) method = init.method.toUpperCase();
|
|
42
|
+
if (init.headers) {
|
|
43
|
+
if (init.headers instanceof Headers) {
|
|
44
|
+
init.headers.forEach((v, k) => {
|
|
45
|
+
rawHeaders[k] = v;
|
|
46
|
+
});
|
|
47
|
+
} else if (Array.isArray(init.headers)) {
|
|
48
|
+
init.headers.forEach(([k, v]) => {
|
|
49
|
+
rawHeaders[k] = v;
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
Object.entries(init.headers).forEach(([k, v]) => {
|
|
53
|
+
rawHeaders[k] = String(v);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (init.body && typeof init.body === "string") {
|
|
58
|
+
reqBodyText = init.body;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const { sanitizedUrl, queryParams, wasRedacted: queryRedacted } = sanitizeUrlAndQuery(
|
|
62
|
+
urlString,
|
|
63
|
+
DEFAULT_DENY_QUERY_PARAMS
|
|
64
|
+
);
|
|
65
|
+
const { sanitized: sanitizedReqHeaders, wasRedacted: headersRedacted } = redactHeaders(
|
|
66
|
+
rawHeaders,
|
|
67
|
+
DEFAULT_DENY_HEADERS
|
|
68
|
+
);
|
|
69
|
+
const { preview: reqBodyPreview, wasRedacted: bodyRedacted } = sanitizeBodyPreview(
|
|
70
|
+
reqBodyText,
|
|
71
|
+
25e3,
|
|
72
|
+
DEFAULT_DENY_JSON_KEYS
|
|
73
|
+
);
|
|
74
|
+
const event = {
|
|
75
|
+
schemaVersion: "1.0.0",
|
|
76
|
+
eventId,
|
|
77
|
+
timestamp,
|
|
78
|
+
phase: "start",
|
|
79
|
+
runtime: "node",
|
|
80
|
+
sourceAdapter: "server-fetch",
|
|
81
|
+
request: {
|
|
82
|
+
method,
|
|
83
|
+
url: urlString,
|
|
84
|
+
sanitizedUrl,
|
|
85
|
+
queryParams,
|
|
86
|
+
headers: sanitizedReqHeaders,
|
|
87
|
+
bodyPreview: reqBodyPreview,
|
|
88
|
+
bodySize: reqBodyText ? reqBodyText.length : 0,
|
|
89
|
+
isTruncated: false
|
|
90
|
+
},
|
|
91
|
+
timing: {
|
|
92
|
+
startTime
|
|
93
|
+
},
|
|
94
|
+
context: {
|
|
95
|
+
attributionMethod: "stack-derived",
|
|
96
|
+
confidence: "medium"
|
|
97
|
+
},
|
|
98
|
+
flags: {
|
|
99
|
+
isInternal: false,
|
|
100
|
+
isRedacted: queryRedacted || headersRedacted || bodyRedacted,
|
|
101
|
+
isTruncated: false,
|
|
102
|
+
isInferred: false
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
serverEventQueue.push(event);
|
|
106
|
+
if (serverEventQueue.length > MAX_SERVER_EVENTS) {
|
|
107
|
+
serverEventQueue.shift();
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const response = await originalGlobalFetch.call(globalThis, input, init);
|
|
111
|
+
const endTime = performance.now();
|
|
112
|
+
const duration = Math.round(endTime - startTime);
|
|
113
|
+
const resHeaders = {};
|
|
114
|
+
response.headers.forEach((v, k) => {
|
|
115
|
+
resHeaders[k] = v;
|
|
116
|
+
});
|
|
117
|
+
const { sanitized: sanitizedResHeaders, wasRedacted: resHeadersRedacted } = redactHeaders(
|
|
118
|
+
resHeaders,
|
|
119
|
+
DEFAULT_DENY_HEADERS
|
|
120
|
+
);
|
|
121
|
+
const contentType = response.headers.get("content-type") || "";
|
|
122
|
+
let resBodyPreview = null;
|
|
123
|
+
if (contentType.includes("application/json") || contentType.includes("text/")) {
|
|
124
|
+
try {
|
|
125
|
+
const clone = response.clone();
|
|
126
|
+
const raw = await clone.text();
|
|
127
|
+
const sanitized = sanitizeBodyPreview(raw, 25e3, DEFAULT_DENY_JSON_KEYS);
|
|
128
|
+
resBodyPreview = sanitized.preview;
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
event.phase = response.ok ? "complete" : "error";
|
|
133
|
+
event.timing.endTime = endTime;
|
|
134
|
+
event.timing.duration = duration;
|
|
135
|
+
event.flags.isSlow = duration >= 1e3;
|
|
136
|
+
event.flags.isRedacted = event.flags.isRedacted || resHeadersRedacted;
|
|
137
|
+
event.response = {
|
|
138
|
+
status: response.status,
|
|
139
|
+
statusText: response.statusText,
|
|
140
|
+
headers: sanitizedResHeaders,
|
|
141
|
+
bodyPreview: resBodyPreview,
|
|
142
|
+
contentType,
|
|
143
|
+
bodySize: resBodyPreview ? resBodyPreview.length : 0,
|
|
144
|
+
isTruncated: false
|
|
145
|
+
};
|
|
146
|
+
return response;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
const endTime = performance.now();
|
|
149
|
+
event.phase = "error";
|
|
150
|
+
event.timing.endTime = endTime;
|
|
151
|
+
event.timing.duration = Math.round(endTime - startTime);
|
|
152
|
+
event.error = {
|
|
153
|
+
name: err instanceof Error ? err.name : "ServerError",
|
|
154
|
+
message: err instanceof Error ? err.message : String(err)
|
|
155
|
+
};
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
return () => {
|
|
160
|
+
if (isServerInstrumented && originalGlobalFetch) {
|
|
161
|
+
globalThis.fetch = originalGlobalFetch;
|
|
162
|
+
originalGlobalFetch = null;
|
|
163
|
+
isServerInstrumented = false;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function createNextTraceRouteHandler() {
|
|
168
|
+
return async function GET() {
|
|
169
|
+
if (process.env.NODE_ENV !== "development") {
|
|
170
|
+
return new Response(JSON.stringify({ error: "Forbidden in production" }), {
|
|
171
|
+
status: 403,
|
|
172
|
+
headers: { "Content-Type": "application/json" }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const events = [...serverEventQueue];
|
|
176
|
+
serverEventQueue.length = 0;
|
|
177
|
+
return new Response(JSON.stringify({ events }), {
|
|
178
|
+
status: 200,
|
|
179
|
+
headers: {
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
"Cache-Control": "no-store, no-cache",
|
|
182
|
+
"x-nexttrace-internal": "true"
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export {
|
|
188
|
+
createNextTraceRouteHandler,
|
|
189
|
+
instrumentServerFetch
|
|
190
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nexttrace/next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js integration for NextTrace with dev-only gating, server fetch collection, and overlay component",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server.d.ts",
|
|
16
|
+
"import": "./dist/server.mjs",
|
|
17
|
+
"require": "./dist/server.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"nextjs",
|
|
25
|
+
"app-router",
|
|
26
|
+
"devtools",
|
|
27
|
+
"nexttrace",
|
|
28
|
+
"tracing"
|
|
29
|
+
],
|
|
30
|
+
"author": "NextTrace Team",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@nexttrace/core": "0.1.0",
|
|
37
|
+
"@nexttrace/devtools": "0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"next": ">=13.0.0 || >=14.0.0 || >=15.0.0",
|
|
41
|
+
"react": ">=18.0.0 || >=19.0.0",
|
|
42
|
+
"react-dom": ">=18.0.0 || >=19.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.14.0",
|
|
46
|
+
"@types/react": "^18.3.5",
|
|
47
|
+
"@types/react-dom": "^18.3.0",
|
|
48
|
+
"next": "^14.2.13",
|
|
49
|
+
"react": "^18.3.1",
|
|
50
|
+
"react-dom": "^18.3.1",
|
|
51
|
+
"tsup": "^8.2.4",
|
|
52
|
+
"typescript": "^5.5.4"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"dev": "tsup --watch",
|
|
57
|
+
"lint": "tsc --noEmit",
|
|
58
|
+
"typecheck": "tsc --noEmit"
|
|
59
|
+
}
|
|
60
|
+
}
|