@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 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 import_react = require("@guidelytech/react");
30
+ var import_react2 = require("@guidelytech/react");
30
31
  var import_navigation = require("next/navigation");
31
- var import_react2 = require("react");
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, import_react.useGuidely)();
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, import_react2.useRef)(true);
40
- (0, import_react2.useEffect)(() => {
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, import_jsx_runtime.jsx)(import_react2.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Listener, {}) });
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 ?? void 0,
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")
@@ -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: (env.NEXT_PUBLIC_GUIDELY_ENV as \"development\" | \"staging\" | \"production\" | undefined) ?? undefined,\n apiUrl: env.NEXT_PUBLIC_GUIDELY_API_URL || undefined,\n release: env.NEXT_PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA2B;AAC3B,wBAA6C;AAC7C,IAAAA,gBAA4C;AAE5C,0BAAc,+BAJd;AA8BM;AAxBN,SAAS,WAAW;AAClB,QAAM,aAAS,yBAAW;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,4CAAC,0BAAS,UAAU,MAClB,sDAAC,YAAS,GACZ;AAEJ;AAGO,SAAS,WAAW,MAA0C,OAAO,YAAY,cAAc,QAAQ,MAAM,CAAC,GAAG;AACtH,SAAO;AAAA,IACL,WAAW,IAAI,kCAAkC;AAAA,IACjD,aAAc,IAAI,2BAAoF;AAAA,IACtG,QAAQ,IAAI,+BAA+B;AAAA,IAC3C,SAAS,IAAI,+BAA+B;AAAA,EAC9C;AACF;","names":["import_react"]}
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: "development" | "staging" | "production" | undefined;
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: "development" | "staging" | "production" | undefined;
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
- import { jsx } from "react/jsx-runtime";
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
- useEffect(() => {
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__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Listener, {}) });
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 ?? void 0,
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: (env.NEXT_PUBLIC_GUIDELY_ENV as \"development\" | \"staging\" | \"production\" | undefined) ?? undefined,\n apiUrl: env.NEXT_PUBLIC_GUIDELY_API_URL || undefined,\n release: env.NEXT_PUBLIC_GUIDELY_RELEASE || undefined,\n };\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,aAAa,uBAAuB;AAC7C,SAAS,UAAU,WAAW,cAAc;AAE5C,cAAc;AA0BR;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,YAAU,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,oBAAC,YAAS,UAAU,MAClB,8BAAC,YAAS,GACZ;AAEJ;AAGO,SAAS,WAAW,MAA0C,OAAO,YAAY,cAAc,QAAQ,MAAM,CAAC,GAAG;AACtH,SAAO;AAAA,IACL,WAAW,IAAI,kCAAkC;AAAA,IACjD,aAAc,IAAI,2BAAoF;AAAA,IACtG,QAAQ,IAAI,+BAA+B;AAAA,IAC3C,SAAS,IAAI,+BAA+B;AAAA,EAC9C;AACF;","names":[]}
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 = registry();
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 ?? registry().current ?? createServerFlags();
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 ?? registry().current ?? createServerFlags();
264
+ const client = options.client ?? registry2().current ?? createServerFlags();
70
265
  if (client.sdkKey.startsWith("gly_sk_")) {
71
- const reg = registry();
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