@iaportafolio/nextjs 0.1.1 → 0.3.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/dist/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -29,11 +30,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
30
  // src/index.ts
30
31
  var index_exports = {};
31
32
  __export(index_exports, {
33
+ FaroErrorBoundary: () => FaroErrorBoundary,
34
+ addBreadcrumb: () => addBreadcrumb,
35
+ captureException: () => captureException2,
32
36
  captureRequestError: () => captureRequestError,
37
+ close: () => close,
38
+ error: () => error,
33
39
  faro: () => faro,
34
- faroClient: () => faroClient,
40
+ flush: () => flush,
41
+ getClient: () => getClient,
42
+ info: () => info,
35
43
  initFaroClient: () => initFaroClient,
36
- registerFaro: () => registerFaro
44
+ log: () => log,
45
+ registerFaro: () => registerFaro,
46
+ setUser: () => setUser,
47
+ warn: () => warn
37
48
  });
38
49
  module.exports = __toCommonJS(index_exports);
39
50
 
@@ -59,126 +70,375 @@ function captureRequestError(err, request) {
59
70
  });
60
71
  }
61
72
 
62
- // src/client.ts
63
- var client = null;
64
- function newClient(opts) {
65
- const endpoint = opts.endpoint.replace(/\/$/, "");
66
- const queue = [];
67
- const maxBatch = opts.maxBatchSize ?? 100;
68
- let pendingTimer = null;
69
- function scheduleFlush() {
70
- if (pendingTimer) return;
71
- pendingTimer = setTimeout(() => {
72
- pendingTimer = null;
73
- void flush();
74
- }, opts.flushIntervalMs ?? 1500);
75
- }
76
- async function flush() {
77
- if (queue.length === 0) return;
78
- const batch = queue.splice(0, maxBatch);
79
- try {
80
- const body = JSON.stringify({ service: opts.service, logs: batch });
81
- const ok = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && document.visibilityState === "hidden" ? navigator.sendBeacon(
82
- `${endpoint}/api/v1/ingest/logs?_token=${encodeURIComponent(opts.token)}`,
83
- new Blob([body], { type: "application/json" })
84
- ) : false;
73
+ // src/browser-core.ts
74
+ var FaroBrowser = class {
75
+ constructor(opts) {
76
+ this.queue = [];
77
+ this.breadcrumbs = [];
78
+ this.user = null;
79
+ this.timer = null;
80
+ this.cleanup = [];
81
+ this.closed = false;
82
+ this.opts = {
83
+ endpoint: opts.endpoint.replace(/\/$/, ""),
84
+ token: opts.token,
85
+ service: opts.service,
86
+ environment: opts.environment,
87
+ release: opts.release,
88
+ attributes: opts.attributes,
89
+ flushIntervalMs: opts.flushIntervalMs ?? 2e3,
90
+ maxBatchSize: opts.maxBatchSize ?? 100,
91
+ maxQueueSize: opts.maxQueueSize ?? 2e3,
92
+ maxBreadcrumbs: opts.maxBreadcrumbs ?? 30,
93
+ captureUnhandled: opts.captureUnhandled ?? true,
94
+ captureConsole: opts.captureConsole ?? false,
95
+ captureWebVitals: opts.captureWebVitals ?? true,
96
+ captureClicks: opts.captureClicks ?? true,
97
+ captureNavigation: opts.captureNavigation ?? true,
98
+ beforeSend: opts.beforeSend
99
+ };
100
+ if (typeof window === "undefined") {
101
+ return;
102
+ }
103
+ this.timer = setInterval(() => void this.flush(), this.opts.flushIntervalMs);
104
+ if (this.opts.captureUnhandled) this.installErrorHandlers();
105
+ if (this.opts.captureConsole) this.installConsoleCapture();
106
+ if (this.opts.captureWebVitals) this.installWebVitals();
107
+ if (this.opts.captureClicks) this.installClickTracking();
108
+ if (this.opts.captureNavigation) this.installNavigationTracking();
109
+ this.installLifecycleHooks();
110
+ }
111
+ setUser(user) {
112
+ this.user = user;
113
+ }
114
+ addBreadcrumb(crumb) {
115
+ if (this.breadcrumbs.length >= this.opts.maxBreadcrumbs) {
116
+ this.breadcrumbs.shift();
117
+ }
118
+ this.breadcrumbs.push({ ...crumb, timestamp: Date.now() });
119
+ }
120
+ log(entry) {
121
+ if (this.closed) return;
122
+ const attrs = this.composeAttributes(entry.attributes);
123
+ const evt = {
124
+ level: entry.level ?? "INFO",
125
+ message: entry.message,
126
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
127
+ attributes: attrs,
128
+ trace_id: entry.trace_id,
129
+ span_id: entry.span_id
130
+ };
131
+ this.enqueue(evt);
132
+ }
133
+ info(message, attrs) {
134
+ this.log({ level: "INFO", message, attributes: attrs });
135
+ }
136
+ warn(message, attrs) {
137
+ this.log({ level: "WARN", message, attributes: attrs });
138
+ }
139
+ error(message, attrs) {
140
+ this.log({ level: "ERROR", message, attributes: attrs });
141
+ }
142
+ captureException(err, ctx) {
143
+ const e = toError(err);
144
+ this.log({
145
+ level: "ERROR",
146
+ message: ctx?.message ?? `${e.name}: ${e.message}`,
147
+ attributes: {
148
+ "exception.type": e.name,
149
+ "exception.message": e.message,
150
+ "exception.stacktrace": e.stack ?? "",
151
+ ...ctx?.tags ?? {}
152
+ }
153
+ });
154
+ }
155
+ async flush(useBeacon = false) {
156
+ if (this.queue.length === 0) return;
157
+ const batch = this.queue.splice(0, this.opts.maxBatchSize);
158
+ const body = JSON.stringify({ service: this.opts.service, logs: batch });
159
+ const url = `${this.opts.endpoint}/api/v1/ingest/logs`;
160
+ if (useBeacon && typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
161
+ const beaconUrl = `${url}?_token=${encodeURIComponent(this.opts.token)}`;
162
+ const ok = navigator.sendBeacon(beaconUrl, new Blob([body], { type: "application/json" }));
85
163
  if (ok) return;
86
- const res = await fetch(`${endpoint}/api/v1/ingest/logs`, {
164
+ }
165
+ try {
166
+ const res = await fetch(url, {
87
167
  method: "POST",
88
168
  keepalive: true,
89
169
  headers: {
90
- "Authorization": `Bearer ${opts.token}`,
170
+ "Authorization": `Bearer ${this.opts.token}`,
91
171
  "Content-Type": "application/json"
92
172
  },
93
173
  body
94
174
  });
95
- if (!res.ok) {
96
- if (res.status >= 500) queue.unshift(...batch);
175
+ if (!res.ok && res.status >= 500) {
176
+ this.queue.unshift(...batch);
97
177
  }
98
- } catch (_e) {
99
- queue.unshift(...batch);
178
+ } catch {
179
+ this.queue.unshift(...batch);
100
180
  }
101
181
  }
102
- function enqueue(level, message, attributes) {
182
+ close() {
183
+ if (this.closed) return;
184
+ this.closed = true;
185
+ if (this.timer) clearInterval(this.timer);
186
+ this.timer = null;
187
+ for (const fn of this.cleanup) fn();
188
+ this.cleanup = [];
189
+ void this.flush(true);
190
+ }
191
+ enqueue(evt) {
192
+ const processed = this.opts.beforeSend ? this.opts.beforeSend(evt) : evt;
193
+ if (!processed) return;
194
+ if (this.queue.length >= this.opts.maxQueueSize) return;
195
+ this.queue.push(processed);
196
+ }
197
+ composeAttributes(extra) {
103
198
  const attrs = {};
104
- if (opts.attributes) {
105
- for (const [k, v] of Object.entries(opts.attributes)) attrs[k] = String(v);
199
+ if (this.opts.attributes) {
200
+ for (const [k, v] of Object.entries(this.opts.attributes)) attrs[k] = String(v);
106
201
  }
107
- if (opts.environment) attrs["deployment.environment"] = opts.environment;
108
- if (opts.release) attrs["service.version"] = opts.release;
202
+ if (this.opts.environment) attrs["deployment.environment"] = this.opts.environment;
203
+ if (this.opts.release) attrs["service.version"] = this.opts.release;
109
204
  if (typeof window !== "undefined") {
110
205
  attrs["browser.url"] = window.location.href;
111
206
  attrs["browser.userAgent"] = navigator.userAgent;
112
207
  }
113
- if (attributes) {
114
- for (const [k, v] of Object.entries(attributes)) {
208
+ if (this.user) {
209
+ if (this.user.id) attrs["user.id"] = this.user.id;
210
+ if (this.user.email) attrs["user.email"] = this.user.email;
211
+ if (this.user.username) attrs["user.name"] = this.user.username;
212
+ }
213
+ if (this.breadcrumbs.length > 0) {
214
+ attrs["breadcrumbs"] = JSON.stringify(this.breadcrumbs.slice(-this.opts.maxBreadcrumbs));
215
+ }
216
+ if (extra) {
217
+ for (const [k, v] of Object.entries(extra)) {
115
218
  attrs[k] = typeof v === "string" ? v : JSON.stringify(v);
116
219
  }
117
220
  }
118
- queue.push({
119
- level,
120
- message,
121
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
122
- attributes: attrs
123
- });
124
- if (queue.length >= maxBatch) void flush();
125
- else scheduleFlush();
126
- }
127
- return {
128
- log: (e) => enqueue(e.level ?? "INFO", e.message, e.attributes),
129
- info: (m, a) => enqueue("INFO", m, a),
130
- warn: (m, a) => enqueue("WARN", m, a),
131
- error: (m, a) => enqueue("ERROR", m, a),
132
- captureException: (err, ctx) => {
133
- const e = err instanceof Error ? err : new Error(typeof err === "string" ? err : JSON.stringify(err));
134
- enqueue("ERROR", (ctx == null ? void 0 : ctx.message) ?? `${e.name}: ${e.message}`, {
135
- "exception.type": e.name,
136
- "exception.message": e.message,
137
- "exception.stacktrace": e.stack ?? "",
138
- ...(ctx == null ? void 0 : ctx.tags) ?? {}
221
+ return attrs;
222
+ }
223
+ installErrorHandlers() {
224
+ const onError = (ev) => {
225
+ this.captureException(ev.error ?? ev.message, {
226
+ tags: { origin: "window.error", "source.file": ev.filename ?? "", "source.line": String(ev.lineno ?? 0) }
139
227
  });
140
- },
141
- flush
142
- };
143
- }
144
- function initFaroClient(opts) {
145
- if (typeof window === "undefined") {
146
- return {
147
- log() {
148
- },
149
- info() {
150
- },
151
- warn() {
152
- },
153
- error() {
154
- },
155
- captureException() {
156
- },
157
- flush: async () => void 0
158
228
  };
229
+ const onRejection = (ev) => {
230
+ this.captureException(ev.reason, { tags: { origin: "unhandledrejection" } });
231
+ };
232
+ window.addEventListener("error", onError);
233
+ window.addEventListener("unhandledrejection", onRejection);
234
+ this.cleanup.push(() => window.removeEventListener("error", onError));
235
+ this.cleanup.push(() => window.removeEventListener("unhandledrejection", onRejection));
159
236
  }
160
- client = newClient(opts);
161
- window.addEventListener("error", (ev) => {
162
- client == null ? void 0 : client.captureException(ev.error ?? ev.message, { tags: { origin: "window.error" } });
163
- });
164
- window.addEventListener("unhandledrejection", (ev) => {
165
- client == null ? void 0 : client.captureException(ev.reason, { tags: { origin: "unhandledrejection" } });
166
- });
167
- document.addEventListener("visibilitychange", () => {
168
- if (document.visibilityState === "hidden") void (client == null ? void 0 : client.flush());
169
- });
170
- window.addEventListener("pagehide", () => void (client == null ? void 0 : client.flush()));
171
- return client;
237
+ installConsoleCapture() {
238
+ const orig = { error: console.error, warn: console.warn };
239
+ console.error = (...args) => {
240
+ this.addBreadcrumb({ category: "console", message: String(args[0] ?? ""), data: { level: "error" } });
241
+ this.log({ level: "ERROR", message: stringifyArgs(args), attributes: { "console.method": "error" } });
242
+ orig.error.apply(console, args);
243
+ };
244
+ console.warn = (...args) => {
245
+ this.addBreadcrumb({ category: "console", message: String(args[0] ?? ""), data: { level: "warn" } });
246
+ orig.warn.apply(console, args);
247
+ };
248
+ this.cleanup.push(() => {
249
+ console.error = orig.error;
250
+ console.warn = orig.warn;
251
+ });
252
+ }
253
+ installWebVitals() {
254
+ void import("web-vitals").then(({ onLCP, onCLS, onINP, onFCP, onTTFB }) => {
255
+ const report = (name) => (m) => {
256
+ this.log({
257
+ level: "INFO",
258
+ message: `web-vital ${name}`,
259
+ attributes: {
260
+ "metric.name": name,
261
+ "metric.value": m.value,
262
+ "metric.rating": m.rating,
263
+ "metric.id": m.id
264
+ }
265
+ });
266
+ };
267
+ onLCP(report("LCP"));
268
+ onCLS(report("CLS"));
269
+ onINP(report("INP"));
270
+ onFCP(report("FCP"));
271
+ onTTFB(report("TTFB"));
272
+ }).catch(() => {
273
+ });
274
+ }
275
+ installClickTracking() {
276
+ const onClick = (ev) => {
277
+ const target = ev.target;
278
+ if (!target) return;
279
+ const tag = target.tagName?.toLowerCase() ?? "";
280
+ const id = target.id;
281
+ const text = (target.textContent ?? "").trim().slice(0, 60);
282
+ const data = { tag };
283
+ if (id) data.id = id;
284
+ if (text) data.text = text;
285
+ this.addBreadcrumb({ category: "click", message: `${tag}${id ? "#" + id : ""}`, data });
286
+ };
287
+ window.addEventListener("click", onClick, { capture: true, passive: true });
288
+ this.cleanup.push(() => window.removeEventListener("click", onClick, { capture: true }));
289
+ }
290
+ installNavigationTracking() {
291
+ const log2 = (from, to, method) => {
292
+ if (from === to) return;
293
+ this.addBreadcrumb({ category: "navigation", message: `${from} \u2192 ${to}`, data: { method, to } });
294
+ };
295
+ const origPush = history.pushState;
296
+ const origReplace = history.replaceState;
297
+ history.pushState = function(...args) {
298
+ const from = location.href;
299
+ const ret = origPush.apply(this, args);
300
+ log2(from, location.href, "pushState");
301
+ return ret;
302
+ };
303
+ history.replaceState = function(...args) {
304
+ const from = location.href;
305
+ const ret = origReplace.apply(this, args);
306
+ log2(from, location.href, "replaceState");
307
+ return ret;
308
+ };
309
+ const onPop = () => log2("", location.href, "popstate");
310
+ window.addEventListener("popstate", onPop);
311
+ this.cleanup.push(() => {
312
+ history.pushState = origPush;
313
+ history.replaceState = origReplace;
314
+ window.removeEventListener("popstate", onPop);
315
+ });
316
+ }
317
+ installLifecycleHooks() {
318
+ const onHide = () => {
319
+ if (document.visibilityState === "hidden") void this.flush(true);
320
+ };
321
+ const onPageHide = () => void this.flush(true);
322
+ document.addEventListener("visibilitychange", onHide);
323
+ window.addEventListener("pagehide", onPageHide);
324
+ this.cleanup.push(() => document.removeEventListener("visibilitychange", onHide));
325
+ this.cleanup.push(() => window.removeEventListener("pagehide", onPageHide));
326
+ }
327
+ };
328
+ function toError(err) {
329
+ if (err instanceof Error) return err;
330
+ if (typeof err === "string") return new Error(err);
331
+ try {
332
+ return new Error(JSON.stringify(err));
333
+ } catch {
334
+ return new Error(String(err));
335
+ }
336
+ }
337
+ function stringifyArgs(args) {
338
+ return args.map((a) => typeof a === "string" ? a : a instanceof Error ? a.stack ?? a.message : safeJson(a)).join(" ");
339
+ }
340
+ function safeJson(v) {
341
+ try {
342
+ return JSON.stringify(v);
343
+ } catch {
344
+ return String(v);
345
+ }
346
+ }
347
+ var singleton = null;
348
+ function init2(opts) {
349
+ if (singleton) singleton.close();
350
+ singleton = new FaroBrowser(opts);
351
+ return singleton;
352
+ }
353
+ function getClient() {
354
+ if (!singleton) throw new Error("faro: init() must be called before use");
355
+ return singleton;
356
+ }
357
+ function log(entry) {
358
+ getClient().log(entry);
172
359
  }
173
- function faroClient() {
174
- if (!client) throw new Error("Hay que llamar a initFaroClient() antes de usarlo");
175
- return client;
360
+ function info(msg, attrs) {
361
+ getClient().info(msg, attrs);
362
+ }
363
+ function warn(msg, attrs) {
364
+ getClient().warn(msg, attrs);
365
+ }
366
+ function error(msg, attrs) {
367
+ getClient().error(msg, attrs);
368
+ }
369
+ function captureException2(err, ctx) {
370
+ getClient().captureException(err, ctx);
371
+ }
372
+ function setUser(user) {
373
+ getClient().setUser(user);
374
+ }
375
+ function addBreadcrumb(crumb) {
376
+ getClient().addBreadcrumb(crumb);
377
+ }
378
+ function flush() {
379
+ return getClient().flush();
380
+ }
381
+ function close() {
382
+ getClient().close();
383
+ }
384
+
385
+ // src/browser-react.tsx
386
+ var React = __toESM(require("react"), 1);
387
+ var FaroErrorBoundary = class extends React.Component {
388
+ constructor() {
389
+ super(...arguments);
390
+ this.state = { error: null };
391
+ this.reset = () => {
392
+ this.setState({ error: null });
393
+ };
394
+ }
395
+ static getDerivedStateFromError(error2) {
396
+ return { error: error2 };
397
+ }
398
+ componentDidCatch(error2, info2) {
399
+ captureException2(error2, {
400
+ tags: {
401
+ origin: "react.error-boundary",
402
+ ...this.props.tags ?? {}
403
+ },
404
+ message: error2.message
405
+ });
406
+ this.props.onError?.(error2, info2);
407
+ }
408
+ render() {
409
+ if (this.state.error) {
410
+ const fb = this.props.fallback;
411
+ if (typeof fb === "function") return fb({ error: this.state.error, reset: this.reset });
412
+ if (fb !== void 0) return fb;
413
+ return null;
414
+ }
415
+ return this.props.children;
416
+ }
417
+ };
418
+
419
+ // src/client.ts
420
+ function initFaroClient(opts) {
421
+ let release = opts.release;
422
+ if (!release && typeof process !== "undefined" && process.env) {
423
+ release = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERSION || void 0;
424
+ }
425
+ return init2({ ...opts, release });
176
426
  }
177
427
  // Annotate the CommonJS export names for ESM import in node:
178
428
  0 && (module.exports = {
429
+ FaroErrorBoundary,
430
+ addBreadcrumb,
431
+ captureException,
179
432
  captureRequestError,
433
+ close,
434
+ error,
180
435
  faro,
181
- faroClient,
436
+ flush,
437
+ getClient,
438
+ info,
182
439
  initFaroClient,
183
- registerFaro
440
+ log,
441
+ registerFaro,
442
+ setUser,
443
+ warn
184
444
  });
@@ -0,0 +1,5 @@
1
+ export { ServerOptions, captureRequestError, registerFaro } from './server.cjs';
2
+ export { Breadcrumb, FaroBrowser, FaroBrowserOptions, FaroErrorBoundary, FaroErrorBoundaryProps, LogEntry, Severity, UserContext, WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, initFaroClient, log, setUser, warn } from './client.cjs';
3
+ import * as faro from '@iaportafolio/node';
4
+ export { faro };
5
+ import 'react';
@@ -0,0 +1,5 @@
1
+ export { ServerOptions, captureRequestError, registerFaro } from './server.js';
2
+ export { Breadcrumb, FaroBrowser, FaroBrowserOptions, FaroErrorBoundary, FaroErrorBoundaryProps, LogEntry, Severity, UserContext, WireEvent, addBreadcrumb, captureException, close, error, flush, getClient, info, initFaroClient, log, setUser, warn } from './client.js';
3
+ import * as faro from '@iaportafolio/node';
4
+ export { faro };
5
+ import 'react';
package/dist/index.js CHANGED
@@ -1,16 +1,36 @@
1
1
  import {
2
- faroClient,
3
- initFaroClient
4
- } from "./chunk-TYH3TMKC.js";
2
+ FaroErrorBoundary,
3
+ addBreadcrumb,
4
+ captureException,
5
+ close,
6
+ error,
7
+ flush,
8
+ getClient,
9
+ info,
10
+ initFaroClient,
11
+ log,
12
+ setUser,
13
+ warn
14
+ } from "./chunk-I3FDJF4L.js";
5
15
  import {
6
16
  captureRequestError,
7
17
  faro,
8
18
  registerFaro
9
19
  } from "./chunk-LFDO56A5.js";
10
20
  export {
21
+ FaroErrorBoundary,
22
+ addBreadcrumb,
23
+ captureException,
11
24
  captureRequestError,
25
+ close,
26
+ error,
12
27
  faro,
13
- faroClient,
28
+ flush,
29
+ getClient,
30
+ info,
14
31
  initFaroClient,
15
- registerFaro
32
+ log,
33
+ registerFaro,
34
+ setUser,
35
+ warn
16
36
  };
package/dist/server.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -0,0 +1,42 @@
1
+ import * as faro from '@iaportafolio/node';
2
+ export { faro };
3
+
4
+ /**
5
+ * Faro para Next.js — lado servidor (instrumentation.ts).
6
+ *
7
+ * Uso (Next.js 13.4+ App Router):
8
+ *
9
+ * // next.config.mjs
10
+ * export default { experimental: { instrumentationHook: true } }; // Next 14- only
11
+ *
12
+ * // instrumentation.ts
13
+ * export async function register() {
14
+ * const { registerFaro } = await import('@iaportafolio/nextjs/server');
15
+ * registerFaro({
16
+ * endpoint: process.env.FARO_ENDPOINT!,
17
+ * token: process.env.FARO_TOKEN!,
18
+ * service: 'mi-next-app',
19
+ * environment: process.env.NODE_ENV,
20
+ * release: process.env.VERCEL_GIT_COMMIT_SHA,
21
+ * });
22
+ * }
23
+ *
24
+ * export async function onRequestError(err: unknown, request: { path: string; method: string }) {
25
+ * const { captureRequestError } = await import('@iaportafolio/nextjs/server');
26
+ * captureRequestError(err, request);
27
+ * }
28
+ */
29
+
30
+ type ServerOptions = faro.FaroOptions;
31
+ declare function registerFaro(opts: ServerOptions): void;
32
+ /**
33
+ * Engánchalo al hook de instrumentación `onRequestError` de Next.js (Next 15+).
34
+ * Reporta el error con el contexto de la request adjunto como tags.
35
+ */
36
+ declare function captureRequestError(err: unknown, request: {
37
+ path?: string;
38
+ method?: string;
39
+ routerKind?: string;
40
+ }): void;
41
+
42
+ export { type ServerOptions, captureRequestError, registerFaro };
@@ -0,0 +1,42 @@
1
+ import * as faro from '@iaportafolio/node';
2
+ export { faro };
3
+
4
+ /**
5
+ * Faro para Next.js — lado servidor (instrumentation.ts).
6
+ *
7
+ * Uso (Next.js 13.4+ App Router):
8
+ *
9
+ * // next.config.mjs
10
+ * export default { experimental: { instrumentationHook: true } }; // Next 14- only
11
+ *
12
+ * // instrumentation.ts
13
+ * export async function register() {
14
+ * const { registerFaro } = await import('@iaportafolio/nextjs/server');
15
+ * registerFaro({
16
+ * endpoint: process.env.FARO_ENDPOINT!,
17
+ * token: process.env.FARO_TOKEN!,
18
+ * service: 'mi-next-app',
19
+ * environment: process.env.NODE_ENV,
20
+ * release: process.env.VERCEL_GIT_COMMIT_SHA,
21
+ * });
22
+ * }
23
+ *
24
+ * export async function onRequestError(err: unknown, request: { path: string; method: string }) {
25
+ * const { captureRequestError } = await import('@iaportafolio/nextjs/server');
26
+ * captureRequestError(err, request);
27
+ * }
28
+ */
29
+
30
+ type ServerOptions = faro.FaroOptions;
31
+ declare function registerFaro(opts: ServerOptions): void;
32
+ /**
33
+ * Engánchalo al hook de instrumentación `onRequestError` de Next.js (Next 15+).
34
+ * Reporta el error con el contexto de la request adjunto como tags.
35
+ */
36
+ declare function captureRequestError(err: unknown, request: {
37
+ path?: string;
38
+ method?: string;
39
+ routerKind?: string;
40
+ }): void;
41
+
42
+ export { type ServerOptions, captureRequestError, registerFaro };