@guidelytech/nextjs 0.15.0 → 0.17.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 +64 -0
- package/dist/index.cjs +32 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +29 -5
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +209 -6
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +122 -1
- package/dist/server.d.ts +122 -1
- package/dist/server.js +200 -5
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -81,3 +81,67 @@ export async function middleware(req) {
|
|
|
81
81
|
if (flags.isEnabled("maintenance-mode")) return Response.redirect(new URL("/maintenance", req.url));
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
|
+
|
|
85
|
+
## Error monitoring
|
|
86
|
+
|
|
87
|
+
What Sentry's Next.js SDK does, in the same places. In the browser the provider's client reports
|
|
88
|
+
uncaught errors; the React error boundary and hooks are re-exported (see `@guidelytech/react`).
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// app/global-error.tsx: errors in the root layout (the provider is gone by then)
|
|
92
|
+
"use client";
|
|
93
|
+
import { GuidelyGlobalError } from "@guidelytech/nextjs";
|
|
94
|
+
export default function GlobalError(props: { error: Error & { digest?: string }; reset: () => void }) {
|
|
95
|
+
return <GuidelyGlobalError {...props} projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!} />;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// app/dashboard/error.tsx: segment errors (inside the provider)
|
|
99
|
+
"use client";
|
|
100
|
+
import { useCaptureException } from "@guidelytech/nextjs";
|
|
101
|
+
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
102
|
+
useCaptureException(error);
|
|
103
|
+
return <button onClick={reset}>Try again</button>;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
On the server, register a server client (`@guidelytech/node`, secret key) once and export
|
|
108
|
+
`onRequestError` (Next.js 15+): every error thrown while rendering, in route handlers, server
|
|
109
|
+
actions and middleware is reported with the request (cookies, `Authorization` and other credential
|
|
110
|
+
headers removed) and the route as tags (`routerKind`, `routePath`, `routeType`).
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// instrumentation.ts
|
|
114
|
+
import { setGuidelyServerClient } from "@guidelytech/nextjs/server";
|
|
115
|
+
|
|
116
|
+
export async function register() {
|
|
117
|
+
if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
118
|
+
const { Guidely } = await import("@guidelytech/node");
|
|
119
|
+
setGuidelyServerClient(new Guidely({ secretKey: process.env.GUIDELY_SECRET_KEY!, environment: "production" }));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export { onRequestError } from "@guidelytech/nextjs/server";
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// Next.js 13/14, or code outside onRequestError's reach: wrap and rethrow.
|
|
127
|
+
import { withGuidely, withServerActionGuidely } from "@guidelytech/nextjs/server";
|
|
128
|
+
export const GET = withGuidely(async (req: Request) => Response.json(await load()), { route: "/api/items/[id]" });
|
|
129
|
+
|
|
130
|
+
"use server";
|
|
131
|
+
import { headers } from "next/headers";
|
|
132
|
+
export const checkout = withServerActionGuidely("checkout", async (form: FormData) => { … }, { headers });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Export | Where | Description |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `GuidelyGlobalError` | client | `app/global-error.tsx` body: reports the error (`mechanism: nextjs.global-error`, `contexts.nextjs.digest` to match the server event) through the live client or one made from its `projectId`/`environment`/`apiUrl`/`release`/`errors` props; renders `children` or a "Try again" page. |
|
|
138
|
+
| `setGuidelyServerClient(client)` | server | The client server captures report to. Any object with `captureException(error, context)` (plus optional `flush()` and `withScope()`) works; `@guidelytech/node`'s `Guidely` is one. This entry imports no Node built-ins, so it loads in the edge runtime too. |
|
|
139
|
+
| `onRequestError` (alias `captureRequestError`), `createOnRequestError(options)` | `instrumentation.ts` | `mechanism: nextjs.onRequestError`, transaction `GET /api/x` or the page route. |
|
|
140
|
+
| `withGuidely(handler, { route? })` | route handlers | Reports and rethrows; sync handlers stay sync. |
|
|
141
|
+
| `withServerActionGuidely(name, action, { headers? })` | server actions | Reports (tag `serverAction`) and rethrows. |
|
|
142
|
+
|
|
143
|
+
Reporting never throws into the request and never holds the response: the flush runs in the
|
|
144
|
+
background (at most `flushTimeoutMs`, 2000 ms) under the request's `waitUntil` when the host has one
|
|
145
|
+
(Next.js 15, Vercel). On other serverless hosts pass `{ waitForFlush: true }` to
|
|
146
|
+
`createOnRequestError`. `redirect()`, `notFound()` and other Next.js control flow are not errors and
|
|
147
|
+
are never reported; an error seen by both a wrapper and `onRequestError` is reported once.
|
package/dist/index.cjs
CHANGED
|
@@ -22,22 +22,46 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
// src/index.tsx
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
|
+
GuidelyGlobalError: () => GuidelyGlobalError,
|
|
25
26
|
GuidelyNavigationListener: () => GuidelyNavigationListener,
|
|
26
27
|
guidelyEnv: () => guidelyEnv
|
|
27
28
|
});
|
|
28
29
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
-
var
|
|
30
|
+
var import_react2 = require("@guidelytech/react");
|
|
30
31
|
var import_navigation = require("next/navigation");
|
|
31
|
-
var
|
|
32
|
+
var import_react3 = require("react");
|
|
32
33
|
__reExport(index_exports, require("@guidelytech/react"), module.exports);
|
|
34
|
+
|
|
35
|
+
// src/global-error.tsx
|
|
36
|
+
var import_sdk_core = require("@guidelytech/sdk-core");
|
|
37
|
+
var import_react = require("react");
|
|
33
38
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
39
|
+
function GuidelyGlobalError({ error, reset, children, projectId, environment, apiUrl, release, errors }) {
|
|
40
|
+
(0, import_react.useEffect)(() => {
|
|
41
|
+
let own = null;
|
|
42
|
+
try {
|
|
43
|
+
let client = import_sdk_core.GuidelyClient.get();
|
|
44
|
+
if (!client && projectId) client = own = (0, import_sdk_core.createGuidely)({ projectId, environment, apiUrl, release, errors });
|
|
45
|
+
client?.captureException(error, { mechanism: { type: "nextjs.global-error", handled: false }, ...error?.digest ? { contexts: { nextjs: { digest: error.digest } } } : {} });
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
return () => own?.destroy();
|
|
49
|
+
}, [error]);
|
|
50
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("html", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("body", { children: children ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontFamily: "system-ui, sans-serif", padding: "4rem 1rem", textAlign: "center" }, children: [
|
|
51
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Something went wrong" }),
|
|
52
|
+
reset ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => reset(), children: "Try again" }) : null
|
|
53
|
+
] }) }) });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/index.tsx
|
|
57
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
34
58
|
function Listener() {
|
|
35
|
-
const client = (0,
|
|
59
|
+
const client = (0, import_react2.useGuidely)();
|
|
36
60
|
const pathname = (0, import_navigation.usePathname)();
|
|
37
61
|
const search = (0, import_navigation.useSearchParams)();
|
|
38
62
|
const query = search?.toString() ?? "";
|
|
39
|
-
const first = (0,
|
|
40
|
-
(0,
|
|
63
|
+
const first = (0, import_react3.useRef)(true);
|
|
64
|
+
(0, import_react3.useEffect)(() => {
|
|
41
65
|
if (first.current) {
|
|
42
66
|
first.current = false;
|
|
43
67
|
return;
|
|
@@ -47,18 +71,19 @@ function Listener() {
|
|
|
47
71
|
return null;
|
|
48
72
|
}
|
|
49
73
|
function GuidelyNavigationListener() {
|
|
50
|
-
return /* @__PURE__ */ (0,
|
|
74
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react3.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Listener, {}) });
|
|
51
75
|
}
|
|
52
76
|
function guidelyEnv(env = typeof process !== "undefined" ? process.env : {}) {
|
|
53
77
|
return {
|
|
54
78
|
projectId: env.NEXT_PUBLIC_GUIDELY_PROJECT_ID ?? "",
|
|
55
|
-
environment: env.NEXT_PUBLIC_GUIDELY_ENV
|
|
79
|
+
environment: env.NEXT_PUBLIC_GUIDELY_ENV || void 0,
|
|
56
80
|
apiUrl: env.NEXT_PUBLIC_GUIDELY_API_URL || void 0,
|
|
57
81
|
release: env.NEXT_PUBLIC_GUIDELY_RELEASE || void 0
|
|
58
82
|
};
|
|
59
83
|
}
|
|
60
84
|
// Annotate the CommonJS export names for ESM import in node:
|
|
61
85
|
0 && (module.exports = {
|
|
86
|
+
GuidelyGlobalError,
|
|
62
87
|
GuidelyNavigationListener,
|
|
63
88
|
guidelyEnv,
|
|
64
89
|
...require("@guidelytech/react")
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import { useGuidely } from \"@guidelytech/react\";\nimport { usePathname, useSearchParams } from \"next/navigation\";\nimport { Suspense, useEffect, useRef } from \"react\";\n\nexport * from \"@guidelytech/react\";\n\nfunction Listener() {\n const client = useGuidely();\n const pathname = usePathname();\n const search = useSearchParams();\n const query = search?.toString() ?? \"\";\n const first = useRef(true);\n useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n client.page(typeof window !== \"undefined\" ? window.location.href : pathname + (query ? `?${query}` : \"\"));\n }, [pathname, query]);\n return null;\n}\n\n/**\n * Explicit App Router navigation bridge: calls `client.page()` whenever the pathname or search\n * params change. Wrapped in `Suspense` because `useSearchParams` requires it. sdk-core also\n * detects navigation through the History API, so this is belt and braces.\n */\nexport function GuidelyNavigationListener() {\n return (\n <Suspense fallback={null}>\n <Listener />\n </Suspense>\n );\n}\n\n/** Reads the public `NEXT_PUBLIC_GUIDELY_*` environment variables (project id, env, API URL, release) into provider props. */\nexport function guidelyEnv(env: Record<string, string | undefined> = typeof process !== \"undefined\" ? process.env : {}) {\n return {\n projectId: env.NEXT_PUBLIC_GUIDELY_PROJECT_ID ?? \"\",\n environment:
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx","../src/global-error.tsx"],"sourcesContent":["import { useGuidely } from \"@guidelytech/react\";\nimport { usePathname, useSearchParams } from \"next/navigation\";\nimport { Suspense, useEffect, useRef } from \"react\";\n\nexport * from \"@guidelytech/react\";\nexport { GuidelyGlobalError } from \"./global-error\";\nexport type { GuidelyGlobalErrorProps } from \"./global-error\";\n\nfunction Listener() {\n const client = useGuidely();\n const pathname = usePathname();\n const search = useSearchParams();\n const query = search?.toString() ?? \"\";\n const first = useRef(true);\n useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n client.page(typeof window !== \"undefined\" ? window.location.href : pathname + (query ? `?${query}` : \"\"));\n }, [pathname, query]);\n return null;\n}\n\n/**\n * Explicit App Router navigation bridge: calls `client.page()` whenever the pathname or search\n * params change. Wrapped in `Suspense` because `useSearchParams` requires it. sdk-core also\n * detects navigation through the History API, so this is belt and braces.\n */\nexport function GuidelyNavigationListener() {\n return (\n <Suspense fallback={null}>\n <Listener />\n </Suspense>\n );\n}\n\n/** Reads the public `NEXT_PUBLIC_GUIDELY_*` environment variables (project id, env, API URL, release) into provider props. */\nexport function guidelyEnv(env: Record<string, string | undefined> = typeof process !== \"undefined\" ? process.env : {}) {\n return {\n projectId: env.NEXT_PUBLIC_GUIDELY_PROJECT_ID ?? \"\",\n environment: env.NEXT_PUBLIC_GUIDELY_ENV || undefined,\n apiUrl: env.NEXT_PUBLIC_GUIDELY_API_URL || undefined,\n release: env.NEXT_PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n","import { createGuidely, GuidelyClient, type GuidelyOptions } from \"@guidelytech/sdk-core\";\nimport { useEffect, type ReactNode } from \"react\";\n\nexport interface GuidelyGlobalErrorProps extends Partial<Pick<GuidelyOptions, \"projectId\" | \"environment\" | \"apiUrl\" | \"release\" | \"errors\">> {\n /** The `error` prop Next.js passes to `app/global-error.tsx`. */\n error: Error & { digest?: string };\n /** The `reset` prop Next.js passes; the default page's \"Try again\" button calls it. */\n reset?: () => void;\n /** Your own page body (default: a short message and a \"Try again\" button). */\n children?: ReactNode;\n}\n\n/**\n * `app/global-error.tsx` for Sentry-style reporting of errors in the root layout:\n *\n * ```tsx\n * \"use client\";\n * import { GuidelyGlobalError } from \"@guidelytech/nextjs\";\n * export default function GlobalError(props: { error: Error & { digest?: string }; reset: () => void }) {\n * return <GuidelyGlobalError {...props} projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!} />;\n * }\n * ```\n *\n * The root layout (and with it `GuidelyProvider` and its client) is gone when this renders, so it\n * reports through the live `createGuidely()` client when there is one, else through a client of\n * its own made from `projectId` (and `environment`, `apiUrl`, `release`, `errors`) that lives as\n * long as the page. Server errors arrive with only their `digest`; it is sent in `contexts.nextjs`\n * so the event matches the server's (`onRequestError`) one.\n */\nexport function GuidelyGlobalError({ error, reset, children, projectId, environment, apiUrl, release, errors }: GuidelyGlobalErrorProps) {\n useEffect(() => {\n let own: GuidelyClient | null = null;\n try {\n let client = GuidelyClient.get();\n if (!client && projectId) client = own = createGuidely({ projectId, environment, apiUrl, release, errors });\n client?.captureException(error, { mechanism: { type: \"nextjs.global-error\", handled: false }, ...(error?.digest ? { contexts: { nextjs: { digest: error.digest } } } : {}) });\n } catch {\n // never break the error page\n }\n return () => own?.destroy();\n }, [error]);\n return (\n <html>\n <body>\n {children ?? (\n <div style={{ fontFamily: \"system-ui, sans-serif\", padding: \"4rem 1rem\", textAlign: \"center\" }}>\n <h2>Something went wrong</h2>\n {reset ? (\n <button type=\"button\" onClick={() => reset()}>\n Try again\n </button>\n ) : null}\n </div>\n )}\n </body>\n </html>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,gBAA2B;AAC3B,wBAA6C;AAC7C,IAAAA,gBAA4C;AAE5C,0BAAc,+BAJd;;;ACAA,sBAAkE;AAClE,mBAA0C;AA4ChC;AAhBH,SAAS,mBAAmB,EAAE,OAAO,OAAO,UAAU,WAAW,aAAa,QAAQ,SAAS,OAAO,GAA4B;AACvI,8BAAU,MAAM;AACd,QAAI,MAA4B;AAChC,QAAI;AACF,UAAI,SAAS,8BAAc,IAAI;AAC/B,UAAI,CAAC,UAAU,UAAW,UAAS,UAAM,+BAAc,EAAE,WAAW,aAAa,QAAQ,SAAS,OAAO,CAAC;AAC1G,cAAQ,iBAAiB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,SAAS,MAAM,GAAG,GAAI,OAAO,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,MAAM,OAAO,EAAE,EAAE,IAAI,CAAC,EAAG,CAAC;AAAA,IAC9K,QAAQ;AAAA,IAER;AACA,WAAO,MAAM,KAAK,QAAQ;AAAA,EAC5B,GAAG,CAAC,KAAK,CAAC;AACV,SACE,4CAAC,UACC,sDAAC,UACE,sBACC,6CAAC,SAAI,OAAO,EAAE,YAAY,yBAAyB,SAAS,aAAa,WAAW,SAAS,GAC3F;AAAA,gDAAC,QAAG,kCAAoB;AAAA,IACvB,QACC,4CAAC,YAAO,MAAK,UAAS,SAAS,MAAM,MAAM,GAAG,uBAE9C,IACE;AAAA,KACN,GAEJ,GACF;AAEJ;;;ADzBM,IAAAC,sBAAA;AAxBN,SAAS,WAAW;AAClB,QAAM,aAAS,0BAAW;AAC1B,QAAM,eAAW,+BAAY;AAC7B,QAAM,aAAS,mCAAgB;AAC/B,QAAM,QAAQ,QAAQ,SAAS,KAAK;AACpC,QAAM,YAAQ,sBAAO,IAAI;AACzB,+BAAU,MAAM;AACd,QAAI,MAAM,SAAS;AACjB,YAAM,UAAU;AAChB;AAAA,IACF;AACA,WAAO,KAAK,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO,YAAY,QAAQ,IAAI,KAAK,KAAK,GAAG;AAAA,EAC1G,GAAG,CAAC,UAAU,KAAK,CAAC;AACpB,SAAO;AACT;AAOO,SAAS,4BAA4B;AAC1C,SACE,6CAAC,0BAAS,UAAU,MAClB,uDAAC,YAAS,GACZ;AAEJ;AAGO,SAAS,WAAW,MAA0C,OAAO,YAAY,cAAc,QAAQ,MAAM,CAAC,GAAG;AACtH,SAAO;AAAA,IACL,WAAW,IAAI,kCAAkC;AAAA,IACjD,aAAa,IAAI,2BAA2B;AAAA,IAC5C,QAAQ,IAAI,+BAA+B;AAAA,IAC3C,SAAS,IAAI,+BAA+B;AAAA,EAC9C;AACF;","names":["import_react","import_jsx_runtime"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
import * as react$1 from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
export * from '@guidelytech/react';
|
|
4
|
+
import { GuidelyOptions } from '@guidelytech/sdk-core';
|
|
5
|
+
|
|
6
|
+
interface GuidelyGlobalErrorProps extends Partial<Pick<GuidelyOptions, "projectId" | "environment" | "apiUrl" | "release" | "errors">> {
|
|
7
|
+
/** The `error` prop Next.js passes to `app/global-error.tsx`. */
|
|
8
|
+
error: Error & {
|
|
9
|
+
digest?: string;
|
|
10
|
+
};
|
|
11
|
+
/** The `reset` prop Next.js passes; the default page's "Try again" button calls it. */
|
|
12
|
+
reset?: () => void;
|
|
13
|
+
/** Your own page body (default: a short message and a "Try again" button). */
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `app/global-error.tsx` for Sentry-style reporting of errors in the root layout:
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* "use client";
|
|
21
|
+
* import { GuidelyGlobalError } from "@guidelytech/nextjs";
|
|
22
|
+
* export default function GlobalError(props: { error: Error & { digest?: string }; reset: () => void }) {
|
|
23
|
+
* return <GuidelyGlobalError {...props} projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!} />;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The root layout (and with it `GuidelyProvider` and its client) is gone when this renders, so it
|
|
28
|
+
* reports through the live `createGuidely()` client when there is one, else through a client of
|
|
29
|
+
* its own made from `projectId` (and `environment`, `apiUrl`, `release`, `errors`) that lives as
|
|
30
|
+
* long as the page. Server errors arrive with only their `digest`; it is sent in `contexts.nextjs`
|
|
31
|
+
* so the event matches the server's (`onRequestError`) one.
|
|
32
|
+
*/
|
|
33
|
+
declare function GuidelyGlobalError({ error, reset, children, projectId, environment, apiUrl, release, errors }: GuidelyGlobalErrorProps): react$1.JSX.Element;
|
|
3
34
|
|
|
4
35
|
/**
|
|
5
36
|
* Explicit App Router navigation bridge: calls `client.page()` whenever the pathname or search
|
|
@@ -10,9 +41,9 @@ declare function GuidelyNavigationListener(): react$1.JSX.Element;
|
|
|
10
41
|
/** Reads the public `NEXT_PUBLIC_GUIDELY_*` environment variables (project id, env, API URL, release) into provider props. */
|
|
11
42
|
declare function guidelyEnv(env?: Record<string, string | undefined>): {
|
|
12
43
|
projectId: string;
|
|
13
|
-
environment:
|
|
44
|
+
environment: string | undefined;
|
|
14
45
|
apiUrl: string | undefined;
|
|
15
46
|
release: string | undefined;
|
|
16
47
|
};
|
|
17
48
|
|
|
18
|
-
export { GuidelyNavigationListener, guidelyEnv };
|
|
49
|
+
export { GuidelyGlobalError, type GuidelyGlobalErrorProps, GuidelyNavigationListener, guidelyEnv };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
import * as react$1 from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
export * from '@guidelytech/react';
|
|
4
|
+
import { GuidelyOptions } from '@guidelytech/sdk-core';
|
|
5
|
+
|
|
6
|
+
interface GuidelyGlobalErrorProps extends Partial<Pick<GuidelyOptions, "projectId" | "environment" | "apiUrl" | "release" | "errors">> {
|
|
7
|
+
/** The `error` prop Next.js passes to `app/global-error.tsx`. */
|
|
8
|
+
error: Error & {
|
|
9
|
+
digest?: string;
|
|
10
|
+
};
|
|
11
|
+
/** The `reset` prop Next.js passes; the default page's "Try again" button calls it. */
|
|
12
|
+
reset?: () => void;
|
|
13
|
+
/** Your own page body (default: a short message and a "Try again" button). */
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `app/global-error.tsx` for Sentry-style reporting of errors in the root layout:
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* "use client";
|
|
21
|
+
* import { GuidelyGlobalError } from "@guidelytech/nextjs";
|
|
22
|
+
* export default function GlobalError(props: { error: Error & { digest?: string }; reset: () => void }) {
|
|
23
|
+
* return <GuidelyGlobalError {...props} projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!} />;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The root layout (and with it `GuidelyProvider` and its client) is gone when this renders, so it
|
|
28
|
+
* reports through the live `createGuidely()` client when there is one, else through a client of
|
|
29
|
+
* its own made from `projectId` (and `environment`, `apiUrl`, `release`, `errors`) that lives as
|
|
30
|
+
* long as the page. Server errors arrive with only their `digest`; it is sent in `contexts.nextjs`
|
|
31
|
+
* so the event matches the server's (`onRequestError`) one.
|
|
32
|
+
*/
|
|
33
|
+
declare function GuidelyGlobalError({ error, reset, children, projectId, environment, apiUrl, release, errors }: GuidelyGlobalErrorProps): react$1.JSX.Element;
|
|
3
34
|
|
|
4
35
|
/**
|
|
5
36
|
* Explicit App Router navigation bridge: calls `client.page()` whenever the pathname or search
|
|
@@ -10,9 +41,9 @@ declare function GuidelyNavigationListener(): react$1.JSX.Element;
|
|
|
10
41
|
/** Reads the public `NEXT_PUBLIC_GUIDELY_*` environment variables (project id, env, API URL, release) into provider props. */
|
|
11
42
|
declare function guidelyEnv(env?: Record<string, string | undefined>): {
|
|
12
43
|
projectId: string;
|
|
13
|
-
environment:
|
|
44
|
+
environment: string | undefined;
|
|
14
45
|
apiUrl: string | undefined;
|
|
15
46
|
release: string | undefined;
|
|
16
47
|
};
|
|
17
48
|
|
|
18
|
-
export { GuidelyNavigationListener, guidelyEnv };
|
|
49
|
+
export { GuidelyGlobalError, type GuidelyGlobalErrorProps, GuidelyNavigationListener, guidelyEnv };
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,39 @@
|
|
|
3
3
|
// src/index.tsx
|
|
4
4
|
import { useGuidely } from "@guidelytech/react";
|
|
5
5
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
6
|
-
import { Suspense, useEffect, useRef } from "react";
|
|
6
|
+
import { Suspense, useEffect as useEffect2, useRef } from "react";
|
|
7
7
|
export * from "@guidelytech/react";
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
// src/global-error.tsx
|
|
10
|
+
import { createGuidely, GuidelyClient } from "@guidelytech/sdk-core";
|
|
11
|
+
import { useEffect } from "react";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
function GuidelyGlobalError({ error, reset, children, projectId, environment, apiUrl, release, errors }) {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let own = null;
|
|
16
|
+
try {
|
|
17
|
+
let client = GuidelyClient.get();
|
|
18
|
+
if (!client && projectId) client = own = createGuidely({ projectId, environment, apiUrl, release, errors });
|
|
19
|
+
client?.captureException(error, { mechanism: { type: "nextjs.global-error", handled: false }, ...error?.digest ? { contexts: { nextjs: { digest: error.digest } } } : {} });
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
return () => own?.destroy();
|
|
23
|
+
}, [error]);
|
|
24
|
+
return /* @__PURE__ */ jsx("html", { children: /* @__PURE__ */ jsx("body", { children: children ?? /* @__PURE__ */ jsxs("div", { style: { fontFamily: "system-ui, sans-serif", padding: "4rem 1rem", textAlign: "center" }, children: [
|
|
25
|
+
/* @__PURE__ */ jsx("h2", { children: "Something went wrong" }),
|
|
26
|
+
reset ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => reset(), children: "Try again" }) : null
|
|
27
|
+
] }) }) });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/index.tsx
|
|
31
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
9
32
|
function Listener() {
|
|
10
33
|
const client = useGuidely();
|
|
11
34
|
const pathname = usePathname();
|
|
12
35
|
const search = useSearchParams();
|
|
13
36
|
const query = search?.toString() ?? "";
|
|
14
37
|
const first = useRef(true);
|
|
15
|
-
|
|
38
|
+
useEffect2(() => {
|
|
16
39
|
if (first.current) {
|
|
17
40
|
first.current = false;
|
|
18
41
|
return;
|
|
@@ -22,17 +45,18 @@ function Listener() {
|
|
|
22
45
|
return null;
|
|
23
46
|
}
|
|
24
47
|
function GuidelyNavigationListener() {
|
|
25
|
-
return /* @__PURE__ */
|
|
48
|
+
return /* @__PURE__ */ jsx2(Suspense, { fallback: null, children: /* @__PURE__ */ jsx2(Listener, {}) });
|
|
26
49
|
}
|
|
27
50
|
function guidelyEnv(env = typeof process !== "undefined" ? process.env : {}) {
|
|
28
51
|
return {
|
|
29
52
|
projectId: env.NEXT_PUBLIC_GUIDELY_PROJECT_ID ?? "",
|
|
30
|
-
environment: env.NEXT_PUBLIC_GUIDELY_ENV
|
|
53
|
+
environment: env.NEXT_PUBLIC_GUIDELY_ENV || void 0,
|
|
31
54
|
apiUrl: env.NEXT_PUBLIC_GUIDELY_API_URL || void 0,
|
|
32
55
|
release: env.NEXT_PUBLIC_GUIDELY_RELEASE || void 0
|
|
33
56
|
};
|
|
34
57
|
}
|
|
35
58
|
export {
|
|
59
|
+
GuidelyGlobalError,
|
|
36
60
|
GuidelyNavigationListener,
|
|
37
61
|
guidelyEnv
|
|
38
62
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import { useGuidely } from \"@guidelytech/react\";\nimport { usePathname, useSearchParams } from \"next/navigation\";\nimport { Suspense, useEffect, useRef } from \"react\";\n\nexport * from \"@guidelytech/react\";\n\nfunction Listener() {\n const client = useGuidely();\n const pathname = usePathname();\n const search = useSearchParams();\n const query = search?.toString() ?? \"\";\n const first = useRef(true);\n useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n client.page(typeof window !== \"undefined\" ? window.location.href : pathname + (query ? `?${query}` : \"\"));\n }, [pathname, query]);\n return null;\n}\n\n/**\n * Explicit App Router navigation bridge: calls `client.page()` whenever the pathname or search\n * params change. Wrapped in `Suspense` because `useSearchParams` requires it. sdk-core also\n * detects navigation through the History API, so this is belt and braces.\n */\nexport function GuidelyNavigationListener() {\n return (\n <Suspense fallback={null}>\n <Listener />\n </Suspense>\n );\n}\n\n/** Reads the public `NEXT_PUBLIC_GUIDELY_*` environment variables (project id, env, API URL, release) into provider props. */\nexport function guidelyEnv(env: Record<string, string | undefined> = typeof process !== \"undefined\" ? process.env : {}) {\n return {\n projectId: env.NEXT_PUBLIC_GUIDELY_PROJECT_ID ?? \"\",\n environment:
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx","../src/global-error.tsx"],"sourcesContent":["import { useGuidely } from \"@guidelytech/react\";\nimport { usePathname, useSearchParams } from \"next/navigation\";\nimport { Suspense, useEffect, useRef } from \"react\";\n\nexport * from \"@guidelytech/react\";\nexport { GuidelyGlobalError } from \"./global-error\";\nexport type { GuidelyGlobalErrorProps } from \"./global-error\";\n\nfunction Listener() {\n const client = useGuidely();\n const pathname = usePathname();\n const search = useSearchParams();\n const query = search?.toString() ?? \"\";\n const first = useRef(true);\n useEffect(() => {\n if (first.current) {\n first.current = false;\n return;\n }\n client.page(typeof window !== \"undefined\" ? window.location.href : pathname + (query ? `?${query}` : \"\"));\n }, [pathname, query]);\n return null;\n}\n\n/**\n * Explicit App Router navigation bridge: calls `client.page()` whenever the pathname or search\n * params change. Wrapped in `Suspense` because `useSearchParams` requires it. sdk-core also\n * detects navigation through the History API, so this is belt and braces.\n */\nexport function GuidelyNavigationListener() {\n return (\n <Suspense fallback={null}>\n <Listener />\n </Suspense>\n );\n}\n\n/** Reads the public `NEXT_PUBLIC_GUIDELY_*` environment variables (project id, env, API URL, release) into provider props. */\nexport function guidelyEnv(env: Record<string, string | undefined> = typeof process !== \"undefined\" ? process.env : {}) {\n return {\n projectId: env.NEXT_PUBLIC_GUIDELY_PROJECT_ID ?? \"\",\n environment: env.NEXT_PUBLIC_GUIDELY_ENV || undefined,\n apiUrl: env.NEXT_PUBLIC_GUIDELY_API_URL || undefined,\n release: env.NEXT_PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n","import { createGuidely, GuidelyClient, type GuidelyOptions } from \"@guidelytech/sdk-core\";\nimport { useEffect, type ReactNode } from \"react\";\n\nexport interface GuidelyGlobalErrorProps extends Partial<Pick<GuidelyOptions, \"projectId\" | \"environment\" | \"apiUrl\" | \"release\" | \"errors\">> {\n /** The `error` prop Next.js passes to `app/global-error.tsx`. */\n error: Error & { digest?: string };\n /** The `reset` prop Next.js passes; the default page's \"Try again\" button calls it. */\n reset?: () => void;\n /** Your own page body (default: a short message and a \"Try again\" button). */\n children?: ReactNode;\n}\n\n/**\n * `app/global-error.tsx` for Sentry-style reporting of errors in the root layout:\n *\n * ```tsx\n * \"use client\";\n * import { GuidelyGlobalError } from \"@guidelytech/nextjs\";\n * export default function GlobalError(props: { error: Error & { digest?: string }; reset: () => void }) {\n * return <GuidelyGlobalError {...props} projectId={process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID!} />;\n * }\n * ```\n *\n * The root layout (and with it `GuidelyProvider` and its client) is gone when this renders, so it\n * reports through the live `createGuidely()` client when there is one, else through a client of\n * its own made from `projectId` (and `environment`, `apiUrl`, `release`, `errors`) that lives as\n * long as the page. Server errors arrive with only their `digest`; it is sent in `contexts.nextjs`\n * so the event matches the server's (`onRequestError`) one.\n */\nexport function GuidelyGlobalError({ error, reset, children, projectId, environment, apiUrl, release, errors }: GuidelyGlobalErrorProps) {\n useEffect(() => {\n let own: GuidelyClient | null = null;\n try {\n let client = GuidelyClient.get();\n if (!client && projectId) client = own = createGuidely({ projectId, environment, apiUrl, release, errors });\n client?.captureException(error, { mechanism: { type: \"nextjs.global-error\", handled: false }, ...(error?.digest ? { contexts: { nextjs: { digest: error.digest } } } : {}) });\n } catch {\n // never break the error page\n }\n return () => own?.destroy();\n }, [error]);\n return (\n <html>\n <body>\n {children ?? (\n <div style={{ fontFamily: \"system-ui, sans-serif\", padding: \"4rem 1rem\", textAlign: \"center\" }}>\n <h2>Something went wrong</h2>\n {reset ? (\n <button type=\"button\" onClick={() => reset()}>\n Try again\n </button>\n ) : null}\n </div>\n )}\n </body>\n </html>\n );\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,aAAa,uBAAuB;AAC7C,SAAS,UAAU,aAAAA,YAAW,cAAc;AAE5C,cAAc;;;ACJd,SAAS,eAAe,qBAA0C;AAClE,SAAS,iBAAiC;AA4ChC,SACE,KADF;AAhBH,SAAS,mBAAmB,EAAE,OAAO,OAAO,UAAU,WAAW,aAAa,QAAQ,SAAS,OAAO,GAA4B;AACvI,YAAU,MAAM;AACd,QAAI,MAA4B;AAChC,QAAI;AACF,UAAI,SAAS,cAAc,IAAI;AAC/B,UAAI,CAAC,UAAU,UAAW,UAAS,MAAM,cAAc,EAAE,WAAW,aAAa,QAAQ,SAAS,OAAO,CAAC;AAC1G,cAAQ,iBAAiB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,SAAS,MAAM,GAAG,GAAI,OAAO,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,MAAM,OAAO,EAAE,EAAE,IAAI,CAAC,EAAG,CAAC;AAAA,IAC9K,QAAQ;AAAA,IAER;AACA,WAAO,MAAM,KAAK,QAAQ;AAAA,EAC5B,GAAG,CAAC,KAAK,CAAC;AACV,SACE,oBAAC,UACC,8BAAC,UACE,sBACC,qBAAC,SAAI,OAAO,EAAE,YAAY,yBAAyB,SAAS,aAAa,WAAW,SAAS,GAC3F;AAAA,wBAAC,QAAG,kCAAoB;AAAA,IACvB,QACC,oBAAC,YAAO,MAAK,UAAS,SAAS,MAAM,MAAM,GAAG,uBAE9C,IACE;AAAA,KACN,GAEJ,GACF;AAEJ;;;ADzBM,gBAAAC,YAAA;AAxBN,SAAS,WAAW;AAClB,QAAM,SAAS,WAAW;AAC1B,QAAM,WAAW,YAAY;AAC7B,QAAM,SAAS,gBAAgB;AAC/B,QAAM,QAAQ,QAAQ,SAAS,KAAK;AACpC,QAAM,QAAQ,OAAO,IAAI;AACzB,EAAAC,WAAU,MAAM;AACd,QAAI,MAAM,SAAS;AACjB,YAAM,UAAU;AAChB;AAAA,IACF;AACA,WAAO,KAAK,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO,YAAY,QAAQ,IAAI,KAAK,KAAK,GAAG;AAAA,EAC1G,GAAG,CAAC,UAAU,KAAK,CAAC;AACpB,SAAO;AACT;AAOO,SAAS,4BAA4B;AAC1C,SACE,gBAAAD,KAAC,YAAS,UAAU,MAClB,0BAAAA,KAAC,YAAS,GACZ;AAEJ;AAGO,SAAS,WAAW,MAA0C,OAAO,YAAY,cAAc,QAAQ,MAAM,CAAC,GAAG;AACtH,SAAO;AAAA,IACL,WAAW,IAAI,kCAAkC;AAAA,IACjD,aAAa,IAAI,2BAA2B;AAAA,IAC5C,QAAQ,IAAI,+BAA+B;AAAA,IAC3C,SAAS,IAAI,+BAA+B;AAAA,EAC9C;AACF;","names":["useEffect","jsx","useEffect"]}
|
package/dist/server.cjs
CHANGED
|
@@ -20,13 +20,208 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/server.ts
|
|
21
21
|
var server_exports = {};
|
|
22
22
|
__export(server_exports, {
|
|
23
|
+
captureRequestError: () => captureRequestError,
|
|
24
|
+
captureServerError: () => captureServerError,
|
|
25
|
+
createOnRequestError: () => createOnRequestError,
|
|
23
26
|
createServerFlags: () => createServerFlags,
|
|
24
27
|
getFlags: () => getFlags,
|
|
25
|
-
getFlagsBootstrap: () => getFlagsBootstrap
|
|
28
|
+
getFlagsBootstrap: () => getFlagsBootstrap,
|
|
29
|
+
isNextControlError: () => isNextControlError,
|
|
30
|
+
onRequestError: () => onRequestError,
|
|
31
|
+
setGuidelyServerClient: () => setGuidelyServerClient,
|
|
32
|
+
withGuidely: () => withGuidely,
|
|
33
|
+
withServerActionGuidely: () => withServerActionGuidely
|
|
26
34
|
});
|
|
27
35
|
module.exports = __toCommonJS(server_exports);
|
|
28
36
|
var import_flags = require("@guidelytech/flags");
|
|
37
|
+
|
|
38
|
+
// src/server-errors.ts
|
|
29
39
|
function registry() {
|
|
40
|
+
const g = globalThis;
|
|
41
|
+
return g.__guidelyServerErrors ?? (g.__guidelyServerErrors = { client: null, reported: /* @__PURE__ */ new WeakSet(), warned: false });
|
|
42
|
+
}
|
|
43
|
+
function setGuidelyServerClient(client) {
|
|
44
|
+
registry().client = client;
|
|
45
|
+
}
|
|
46
|
+
var SENSITIVE_HEADER = /^(?:cookie|set-cookie|authorization|proxy-authorization)$|token|secret|password|api[-_]?key|session|csrf/i;
|
|
47
|
+
function safeHeaders(headers) {
|
|
48
|
+
const out = {};
|
|
49
|
+
const add = (name, value) => {
|
|
50
|
+
if (Object.keys(out).length >= 50 || value === void 0 || value === null || SENSITIVE_HEADER.test(name)) return;
|
|
51
|
+
out[name.toLowerCase()] = (Array.isArray(value) ? value.join(", ") : String(value)).slice(0, 1e3);
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
const h = headers;
|
|
55
|
+
if (h && typeof h.forEach === "function" && !Array.isArray(h)) h.forEach((value, name) => add(name, value));
|
|
56
|
+
else if (h && typeof h === "object") for (const [name, value] of Object.entries(h)) add(name, value);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function isNextControlError(error) {
|
|
62
|
+
if (!error || typeof error !== "object") return false;
|
|
63
|
+
const e = error;
|
|
64
|
+
if (e.$$typeof === /* @__PURE__ */ Symbol.for("react.postpone")) return true;
|
|
65
|
+
const digest = typeof e.digest === "string" ? e.digest : typeof e.message === "string" ? e.message : "";
|
|
66
|
+
return /^(?:NEXT_REDIRECT|NEXT_NOT_FOUND|NEXT_HTTP_ERROR_FALLBACK|DYNAMIC_SERVER_USAGE|BAILOUT_TO_CLIENT_SIDE_RENDERING|NEXT_PRERENDER_INTERRUPTED|HANGING_PROMISE_REJECTION)/.test(digest);
|
|
67
|
+
}
|
|
68
|
+
function requestWaitUntil() {
|
|
69
|
+
try {
|
|
70
|
+
const g = globalThis;
|
|
71
|
+
for (const key of ["@next/request-context", "@vercel/request-context"]) {
|
|
72
|
+
const waitUntil = g[Symbol.for(key)]?.get?.()?.waitUntil;
|
|
73
|
+
if (typeof waitUntil === "function") return waitUntil;
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
function flushSoon(client, options) {
|
|
80
|
+
const timeout = options.flushTimeoutMs ?? 2e3;
|
|
81
|
+
if (typeof client.flush !== "function" || timeout <= 0) return void 0;
|
|
82
|
+
let timer;
|
|
83
|
+
const p = Promise.race([
|
|
84
|
+
Promise.resolve().then(() => client.flush()).catch(() => {
|
|
85
|
+
}),
|
|
86
|
+
new Promise((resolve) => {
|
|
87
|
+
timer = setTimeout(resolve, timeout);
|
|
88
|
+
timer.unref?.();
|
|
89
|
+
})
|
|
90
|
+
]).finally(() => clearTimeout(timer));
|
|
91
|
+
const waitUntil = requestWaitUntil();
|
|
92
|
+
if (waitUntil) {
|
|
93
|
+
try {
|
|
94
|
+
waitUntil(p);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return p;
|
|
99
|
+
}
|
|
100
|
+
function captureServerError(error, input, options = {}) {
|
|
101
|
+
try {
|
|
102
|
+
if (isNextControlError(error)) return { eventId: "" };
|
|
103
|
+
const reg = registry();
|
|
104
|
+
const client = options.client ?? reg.client;
|
|
105
|
+
if (!client) {
|
|
106
|
+
if (!reg.warned) {
|
|
107
|
+
reg.warned = true;
|
|
108
|
+
console.warn("[Guidely] A server error was not reported: no server client. Call setGuidelyServerClient(new Guidely({ secretKey, environment })) from register() in instrumentation.ts.");
|
|
109
|
+
}
|
|
110
|
+
return { eventId: "" };
|
|
111
|
+
}
|
|
112
|
+
if (error && typeof error === "object") {
|
|
113
|
+
if (reg.reported.has(error)) return { eventId: "" };
|
|
114
|
+
reg.reported.add(error);
|
|
115
|
+
}
|
|
116
|
+
const digest = error && typeof error === "object" && typeof error.digest === "string" ? error.digest : void 0;
|
|
117
|
+
const nextjs = { ...input.contexts?.nextjs ?? {}, ...digest ? { digest } : {} };
|
|
118
|
+
const ctx = {
|
|
119
|
+
level: "error",
|
|
120
|
+
...input.tags ? { tags: input.tags } : {},
|
|
121
|
+
contexts: { ...input.contexts ?? {}, ...Object.keys(nextjs).length ? { nextjs } : {} },
|
|
122
|
+
...input.request ? { request: input.request } : {},
|
|
123
|
+
mechanism: { type: input.mechanism, handled: false }
|
|
124
|
+
};
|
|
125
|
+
const run = () => client.captureException(error, ctx);
|
|
126
|
+
const id = input.transaction && typeof client.withScope === "function" ? client.withScope((scope) => {
|
|
127
|
+
scope.setTransactionName?.(input.transaction);
|
|
128
|
+
return run();
|
|
129
|
+
}) : run();
|
|
130
|
+
return { eventId: typeof id === "string" ? id : "", flushed: flushSoon(client, options) };
|
|
131
|
+
} catch {
|
|
132
|
+
return { eventId: "" };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
var str = (v) => typeof v === "string" && v ? v : void 0;
|
|
136
|
+
function createOnRequestError(options = {}) {
|
|
137
|
+
return async (error, request, context) => {
|
|
138
|
+
try {
|
|
139
|
+
const tags = {};
|
|
140
|
+
const nextjs = {};
|
|
141
|
+
for (const [k, v] of Object.entries(context ?? {})) {
|
|
142
|
+
if (typeof v !== "string" || !v) continue;
|
|
143
|
+
nextjs[k] = v;
|
|
144
|
+
if (k === "routerKind" || k === "routePath" || k === "routeType") tags[k] = v;
|
|
145
|
+
}
|
|
146
|
+
const method = str(request?.method)?.toUpperCase();
|
|
147
|
+
const route = str(context?.routePath);
|
|
148
|
+
const { flushed } = captureServerError(
|
|
149
|
+
error,
|
|
150
|
+
{
|
|
151
|
+
tags,
|
|
152
|
+
contexts: { nextjs },
|
|
153
|
+
request: { method, url: str(request?.path), headers: safeHeaders(request?.headers) },
|
|
154
|
+
transaction: route ? method && context?.routeType !== "render" ? `${method} ${route}` : route : void 0,
|
|
155
|
+
mechanism: "nextjs.onRequestError"
|
|
156
|
+
},
|
|
157
|
+
options
|
|
158
|
+
);
|
|
159
|
+
if (options.waitForFlush && flushed) await flushed;
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
var onRequestError = createOnRequestError();
|
|
165
|
+
var captureRequestError = onRequestError;
|
|
166
|
+
function requestOf(input) {
|
|
167
|
+
if (!input || typeof input !== "object") return void 0;
|
|
168
|
+
const r = input;
|
|
169
|
+
if (typeof r.url !== "string" || !r.headers) return void 0;
|
|
170
|
+
let path;
|
|
171
|
+
try {
|
|
172
|
+
path = new URL(r.url).pathname;
|
|
173
|
+
} catch {
|
|
174
|
+
path = void 0;
|
|
175
|
+
}
|
|
176
|
+
return { method: str(r.method)?.toUpperCase(), url: r.url, headers: safeHeaders(r.headers), path };
|
|
177
|
+
}
|
|
178
|
+
function withGuidely(handler, options = {}) {
|
|
179
|
+
const report = (error, args) => {
|
|
180
|
+
const req = requestOf(args[0]);
|
|
181
|
+
const route = options.route ?? req?.path;
|
|
182
|
+
captureServerError(error, { tags: { routeType: "route", ...options.route ? { routePath: options.route } : {} }, request: req && { method: req.method, url: req.url, headers: req.headers }, transaction: route ? `${req?.method ? `${req.method} ` : ""}${route}` : void 0, mechanism: "nextjs.route_handler" }, options);
|
|
183
|
+
};
|
|
184
|
+
const wrapped = function(...args) {
|
|
185
|
+
let out;
|
|
186
|
+
try {
|
|
187
|
+
out = handler.apply(this, args);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
report(error, args);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
if (out && typeof out.then === "function") {
|
|
193
|
+
return out.then(void 0, (error) => {
|
|
194
|
+
report(error, args);
|
|
195
|
+
throw error;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
};
|
|
200
|
+
return wrapped;
|
|
201
|
+
}
|
|
202
|
+
function withServerActionGuidely(name, action, options = {}) {
|
|
203
|
+
return async (...args) => {
|
|
204
|
+
try {
|
|
205
|
+
return await action(...args);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!isNextControlError(error)) {
|
|
208
|
+
let headers;
|
|
209
|
+
try {
|
|
210
|
+
const h = typeof options.headers === "function" ? options.headers() : options.headers;
|
|
211
|
+
headers = h ? safeHeaders(await h) : void 0;
|
|
212
|
+
} catch {
|
|
213
|
+
headers = void 0;
|
|
214
|
+
}
|
|
215
|
+
const { flushed } = captureServerError(error, { tags: { routeType: "action", serverAction: name.slice(0, 200) }, contexts: { nextjs: { serverAction: name } }, ...headers ? { request: { method: "POST", headers } } : {}, transaction: `serverAction/${name}`, mechanism: "nextjs.server_action" }, options);
|
|
216
|
+
if (options.waitForFlush && flushed) await flushed;
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/server.ts
|
|
224
|
+
function registry2() {
|
|
30
225
|
const g = globalThis;
|
|
31
226
|
return g.__guidelyServerFlags ?? (g.__guidelyServerFlags = { clients: /* @__PURE__ */ new Map(), current: null, warned: /* @__PURE__ */ new Set() });
|
|
32
227
|
}
|
|
@@ -47,7 +242,7 @@ function createServerFlags(options = {}) {
|
|
|
47
242
|
const apiUrl = options.apiUrl ?? env("GUIDELY_API_URL") ?? env("NEXT_PUBLIC_GUIDELY_API_URL");
|
|
48
243
|
const environment = options.environment ?? env("GUIDELY_ENV") ?? env("NEXT_PUBLIC_GUIDELY_ENV");
|
|
49
244
|
const id = `${sdkKey}|${apiUrl ?? ""}|${environment ?? ""}|${options.pollingMode ?? "auto"}`;
|
|
50
|
-
const reg =
|
|
245
|
+
const reg = registry2();
|
|
51
246
|
let client = reg.clients.get(id);
|
|
52
247
|
if (!client) {
|
|
53
248
|
client = (0, import_flags.createFlagsClient)({ storage: false, ...options, sdkKey, apiUrl, environment, fetch: options.fetch ?? noStoreFetch });
|
|
@@ -61,14 +256,14 @@ function settle(client, timeoutMs) {
|
|
|
61
256
|
return Promise.race([client.ready(), new Promise((r) => setTimeout(r, timeoutMs))]);
|
|
62
257
|
}
|
|
63
258
|
async function getFlags(attributes = {}, options = {}) {
|
|
64
|
-
const client = options.client ??
|
|
259
|
+
const client = options.client ?? registry2().current ?? createServerFlags();
|
|
65
260
|
await settle(client, options.timeoutMs ?? 1500);
|
|
66
261
|
return client.forUser(attributes);
|
|
67
262
|
}
|
|
68
263
|
async function getFlagsBootstrap(options = {}) {
|
|
69
|
-
const client = options.client ??
|
|
264
|
+
const client = options.client ?? registry2().current ?? createServerFlags();
|
|
70
265
|
if (client.sdkKey.startsWith("gly_sk_")) {
|
|
71
|
-
const reg =
|
|
266
|
+
const reg = registry2();
|
|
72
267
|
if (!reg.warned.has(client.sdkKey)) {
|
|
73
268
|
reg.warned.add(client.sdkKey);
|
|
74
269
|
console.warn("[Guidely] getFlagsBootstrap() needs a public-key client (pk_\u2026): a gly_sk_ payload includes server-only flags. Pass { client: createServerFlags({ sdkKey: process.env.NEXT_PUBLIC_GUIDELY_PROJECT_ID }) }.");
|
|
@@ -80,8 +275,16 @@ async function getFlagsBootstrap(options = {}) {
|
|
|
80
275
|
}
|
|
81
276
|
// Annotate the CommonJS export names for ESM import in node:
|
|
82
277
|
0 && (module.exports = {
|
|
278
|
+
captureRequestError,
|
|
279
|
+
captureServerError,
|
|
280
|
+
createOnRequestError,
|
|
83
281
|
createServerFlags,
|
|
84
282
|
getFlags,
|
|
85
|
-
getFlagsBootstrap
|
|
283
|
+
getFlagsBootstrap,
|
|
284
|
+
isNextControlError,
|
|
285
|
+
onRequestError,
|
|
286
|
+
setGuidelyServerClient,
|
|
287
|
+
withGuidely,
|
|
288
|
+
withServerActionGuidely
|
|
86
289
|
});
|
|
87
290
|
//# sourceMappingURL=server.cjs.map
|