@nextlytics/core 0.4.2-canary.105 → 0.4.2-canary.106

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.
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { RequestContext, ServerEventContext, NextlyticsConfig, AnonymousUserResult } from './types.js';
3
3
  import 'next/dist/server/web/spec-extension/cookies';
4
+ import 'next';
4
5
 
5
6
  type ResolveAnonymousUserParams = {
6
7
  ctx: RequestContext;
@@ -2,6 +2,7 @@ import { NextRequest } from 'next/server';
2
2
  import { RequestContext, JavascriptTemplate, NextlyticsEvent, PageViewDelivery, DispatchResult, UserContext } from './types.js';
3
3
  import { NextlyticsConfigWithDefaults } from './config-helpers.js';
4
4
  import 'next/dist/server/web/spec-extension/cookies';
5
+ import 'next';
5
6
 
6
7
  type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext, policyFilter?: PageViewDelivery | "client-actions") => DispatchResult;
7
8
  type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackend } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  /**
6
7
  * ClickHouse backend for Nextlytics
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackendFactory } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  type GoogleAnalyticsBackendOptions = {
6
7
  /** GA4 Measurement ID (e.g. "G-XXXXXXXXXX") */
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackend } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  type GoogleTagManagerBackendOptions = {
6
7
  /** GTM Container ID (e.g. "GTM-XXXXXXX") */
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsEvent, ClientContext, ServerEventContext } from '../../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  declare const tableColumns: readonly [{
6
7
  readonly name: "timestamp";
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackend } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  declare function loggingBackend(): NextlyticsBackend;
6
7
 
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackend } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  type NeonBackendConfig = {
6
7
  databaseUrl: string;
@@ -2,6 +2,7 @@ import { NextlyticsBackend } from '../types.js';
2
2
  export { AnalyticsEventRow, generatePgCreateTableSQL } from './lib/db.js';
3
3
  import 'next/dist/server/web/spec-extension/cookies';
4
4
  import 'next/server';
5
+ import 'next';
5
6
 
6
7
  /**
7
8
  * PostgREST backend for Nextlytics
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackend } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  type PosthogBackendOptions = {
6
7
  /** PostHog project API key */
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsBackend } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  /**
6
7
  * Segment backend for Nextlytics
package/dist/client.d.ts CHANGED
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
3
3
  import { TemplatizedScriptInsertion, JavascriptTemplate } from './types.js';
4
4
  import 'next/dist/server/web/spec-extension/cookies';
5
5
  import 'next/server';
6
+ import 'next';
6
7
 
7
8
  /** Context object for Pages Router integration */
8
9
  type NextlyticsContext = {
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsConfig } from './types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  type NextlyticsConfigWithDefaults = Required<Pick<NextlyticsConfig, "excludeApiCalls" | "eventEndpoint" | "isApiPath" | "backends">> & NextlyticsConfig & {
6
7
  anonymousUsers: Required<NonNullable<NextlyticsConfig["anonymousUsers"]>>;
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ import 'react/jsx-runtime';
8
8
  import 'react';
9
9
  import 'next/dist/server/web/spec-extension/cookies';
10
10
  import 'next/server';
11
+ import 'next';
@@ -3,6 +3,7 @@ import { NextlyticsConfigWithDefaults } from './config-helpers.js';
3
3
  import { DispatchEvent, UpdateEvent, CollectTemplates } from './api-handler.js';
4
4
  import './types.js';
5
5
  import 'next/dist/server/web/spec-extension/cookies';
6
+ import 'next';
6
7
 
7
8
  declare function createNextlyticsMiddleware(config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent, collectTemplates: CollectTemplates): NextMiddleware;
8
9
 
@@ -4,6 +4,7 @@ import 'react';
4
4
  import './types.js';
5
5
  import 'next/dist/server/web/spec-extension/cookies';
6
6
  import 'next/server';
7
+ import 'next';
7
8
 
8
9
  type PagesRouterContext = {
9
10
  req?: {
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsPlugin } from '../types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  type VercelGeoPluginOptions = {
6
7
  /** Property name to store geo data under. Default: "geo" */
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { TemplatizedScriptInsertion } from './types.js';
3
3
  import 'next/dist/server/web/spec-extension/cookies';
4
+ import 'next';
4
5
 
5
6
  declare const headerNames: {
6
7
  readonly pathname: "x-nl-pathname";
package/dist/server.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { NextlyticsConfig, NextlyticsResult, RequestContext } from './types.js';
2
2
  import 'next/dist/server/web/spec-extension/cookies';
3
3
  import 'next/server';
4
+ import 'next';
4
5
 
5
6
  declare function createRequestContext(): Promise<RequestContext>;
6
7
  declare function Nextlytics(userConfig: NextlyticsConfig): NextlyticsResult;
package/dist/server.js CHANGED
@@ -195,15 +195,14 @@ function Nextlytics(userConfig) {
195
195
  const templates = collectTemplates(config, requestCtx);
196
196
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client.NextlyticsClient, { ctx: { requestId: ctx.pageRenderId, scripts: ctx.scripts, templates }, children });
197
197
  }
198
- const analytics = async () => {
199
- const headersList = await (0, import_headers.headers)();
200
- const cookieStore = await (0, import_headers.cookies)();
201
- const pageRenderId = headersList.get(import_server_component_context.headerNames.pageRenderId);
202
- const serverContext = createServerContextFromHeaders(headersList);
198
+ const analytics = async (req) => {
199
+ const source = req ? normalizeRequest(req) : await normalizeFromNextHeaders();
200
+ const pageRenderId = source.headers.get(import_server_component_context.headerNames.pageRenderId) || source.cookies.get(import_server_component_context.LAST_PAGE_RENDER_ID_COOKIE)?.value || void 0;
201
+ const serverContext = buildServerContext(source);
203
202
  const ctx = {
204
- headers: headersList,
205
- cookies: cookieStore,
206
- path: headersList.get(import_server_component_context.headerNames.pathname) || ""
203
+ headers: source.headers,
204
+ cookies: source.cookies,
205
+ path: source.path
207
206
  };
208
207
  const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config });
209
208
  let userContext;
@@ -216,14 +215,14 @@ function Nextlytics(userConfig) {
216
215
  const propsFromCallback = await (0, import_api_handler.getEventProps)(config, ctx, userContext);
217
216
  return {
218
217
  sendEvent: async (eventName, opts) => {
219
- if (!pageRenderId) {
218
+ if (!pageRenderId && !req) {
220
219
  console.error("[Nextlytics] analytics() requires nextlyticsMiddleware");
221
220
  return { ok: false };
222
221
  }
223
222
  const event = {
224
223
  origin: "server",
225
224
  eventId: (0, import_uitils.generateId)(),
226
- parentEventId: pageRenderId,
225
+ ...pageRenderId ? { parentEventId: pageRenderId } : {},
227
226
  type: eventName,
228
227
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
229
228
  anonymousUserId,
@@ -244,32 +243,73 @@ function Nextlytics(userConfig) {
244
243
  NextlyticsServer: Server
245
244
  };
246
245
  }
247
- function createServerContextFromHeaders(headersList) {
246
+ function searchToRecord(params) {
247
+ const out = {};
248
+ params.forEach((value, key) => {
249
+ (out[key] ?? (out[key] = [])).push(value);
250
+ });
251
+ return out;
252
+ }
253
+ function isNextApiRequest(req) {
254
+ return typeof req.headers?.get !== "function";
255
+ }
256
+ async function normalizeFromNextHeaders() {
257
+ const [_cookies, _headers] = await Promise.all([(0, import_headers.cookies)(), (0, import_headers.headers)()]);
258
+ return {
259
+ headers: _headers,
260
+ cookies: _cookies,
261
+ path: _headers.get(import_server_component_context.headerNames.pathname) || "",
262
+ search: searchToRecord(new URLSearchParams(_headers.get(import_server_component_context.headerNames.search) || "")),
263
+ method: "GET"
264
+ };
265
+ }
266
+ function normalizeRequest(req) {
267
+ if (!isNextApiRequest(req)) {
268
+ return {
269
+ headers: req.headers,
270
+ cookies: req.cookies,
271
+ path: req.nextUrl.pathname,
272
+ search: searchToRecord(req.nextUrl.searchParams),
273
+ method: req.method
274
+ };
275
+ }
276
+ const headersList = new Headers();
277
+ for (const [key, value] of Object.entries(req.headers)) {
278
+ if (value !== void 0) {
279
+ headersList.set(key, Array.isArray(value) ? value.join(", ") : value);
280
+ }
281
+ }
282
+ const cookieMap = req.cookies || {};
283
+ const cookieStore = {
284
+ get: (name) => {
285
+ const value = cookieMap[name];
286
+ return value === void 0 ? void 0 : { name, value };
287
+ },
288
+ getAll: () => Object.entries(cookieMap).map(([name, value]) => ({ name, value })),
289
+ has: (name) => name in cookieMap
290
+ };
291
+ const url = new URL(req.url || "/", `http://${headersList.get("host") || "localhost"}`);
292
+ return {
293
+ headers: headersList,
294
+ cookies: cookieStore,
295
+ path: url.pathname,
296
+ search: searchToRecord(url.searchParams),
297
+ method: req.method || "GET"
298
+ };
299
+ }
300
+ function buildServerContext(source) {
248
301
  const rawHeaders = {};
249
- headersList.forEach((value, key) => {
302
+ source.headers.forEach((value, key) => {
250
303
  rawHeaders[key] = value;
251
304
  });
252
- const requestHeaders = (0, import_headers2.removeSensitiveHeaders)(rawHeaders);
253
- const pathname = headersList.get(import_server_component_context.headerNames.pathname) || "";
254
- const search = headersList.get(import_server_component_context.headerNames.search) || "";
255
- const searchParams = {};
256
- if (search) {
257
- const params = new URLSearchParams(search);
258
- params.forEach((value, key) => {
259
- if (!searchParams[key]) {
260
- searchParams[key] = [];
261
- }
262
- searchParams[key].push(value);
263
- });
264
- }
265
305
  return {
266
306
  collectedAt: /* @__PURE__ */ new Date(),
267
- host: headersList.get("host") || "",
268
- method: "GET",
269
- path: pathname,
270
- search: searchParams,
271
- ip: headersList.get("x-forwarded-for")?.split(",")[0]?.trim() || "",
272
- requestHeaders,
307
+ host: source.headers.get("host") || "",
308
+ method: source.method,
309
+ path: source.path,
310
+ search: source.search,
311
+ ip: source.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "",
312
+ requestHeaders: (0, import_headers2.removeSensitiveHeaders)(rawHeaders),
273
313
  responseHeaders: {}
274
314
  };
275
315
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { RequestCookies } from 'next/dist/server/web/spec-extension/cookies';
2
- import { NextMiddleware } from 'next/server';
2
+ import { NextRequest, NextMiddleware } from 'next/server';
3
+ import { NextApiRequest } from 'next';
3
4
 
4
5
  /** Server-side request context collected in middleware */
5
6
  interface ServerEventContext {
@@ -227,7 +228,7 @@ type NextlyticsBackend = {
227
228
  type NextlyticsBackendFactory = (ctx: RequestContext) => NextlyticsBackend;
228
229
  /** Server-side analytics API */
229
230
  type NextlyticsServerSide = {
230
- /** Send custom event from server component/action */
231
+ /** Send custom event from a server component, server action, or API route */
231
232
  sendEvent: (eventName: string, opts?: {
232
233
  props?: Record<string, unknown>;
233
234
  }) => Promise<{
@@ -272,8 +273,15 @@ type ClientRequestResult = {
272
273
  };
273
274
  /** Return value from Nextlytics() */
274
275
  type NextlyticsResult = {
275
- /** Get server-side analytics API */
276
- analytics: () => Promise<NextlyticsServerSide>;
276
+ /** Get server-side analytics API.
277
+ *
278
+ * App Router (server components, server actions, route handlers): call with no
279
+ * argument — context is read from `next/headers`.
280
+ *
281
+ * Pages Router API routes: `next/headers` is unavailable there, so pass the
282
+ * request (`analytics(req)`). A NextRequest (App Router route handler) is also
283
+ * accepted. */
284
+ analytics: (req?: NextRequest | NextApiRequest) => Promise<NextlyticsServerSide>;
277
285
  /** Middleware to intercept requests */
278
286
  middleware: NextMiddleware;
279
287
  /** Manually dispatch event (returns two-phase result) */
package/dist/uitils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { NextRequest } from 'next/server';
2
2
  import { ServerEventContext } from './types.js';
3
3
  import 'next/dist/server/web/spec-extension/cookies';
4
+ import 'next';
4
5
 
5
6
  /** Returns the full installed Next.js version string (e.g. "16.1.6"), or undefined. */
6
7
  declare function getNextVersion(): string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextlytics/core",
3
- "version": "0.4.2-canary.105",
3
+ "version": "0.4.2-canary.106",
4
4
  "description": "Analytics library for Next.js",
5
5
  "license": "MIT",
6
6
  "repository": {