@nextlytics/core 0.2.2 → 0.3.0-canary.80

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.
@@ -25,19 +25,7 @@ var import_server = require("next/server");
25
25
  var import_server_component_context = require("./server-component-context");
26
26
  var import_uitils = require("./uitils");
27
27
  var import_anonymous_user = require("./anonymous-user");
28
- function resolveBackends(config, ctx) {
29
- const backends = config.backends || [];
30
- return backends.map((backend) => typeof backend === "function" ? backend(ctx) : backend).filter((b) => b !== null);
31
- }
32
- function collectTemplates(backends) {
33
- const templates = {};
34
- for (const backend of backends) {
35
- if (backend.getClientSideTemplates) {
36
- Object.assign(templates, backend.getClientSideTemplates());
37
- }
38
- }
39
- return templates;
40
- }
28
+ var import_api_handler = require("./api-handler");
41
29
  function createRequestContext(request) {
42
30
  return {
43
31
  headers: request.headers,
@@ -49,66 +37,89 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
49
37
  return async (request) => {
50
38
  const pathname = request.nextUrl.pathname;
51
39
  const reqInfo = (0, import_uitils.getRequestInfo)(request);
40
+ const middlewareDebug = config.debug || process.env.NEXTLYTICS_MIDDLEWARE_DEBUG === "true";
41
+ if (middlewareDebug) {
42
+ const headers = request.headers;
43
+ const debugHeaders = {};
44
+ headers.forEach((value, key) => {
45
+ debugHeaders[key] = value;
46
+ });
47
+ console.log("[Nextlytics][middleware]", {
48
+ url: request.url,
49
+ pathname,
50
+ search: request.nextUrl.search,
51
+ method: request.method,
52
+ nextVersion: (0, import_uitils.getNextVersion)(),
53
+ destination: request.destination,
54
+ referrer: request.referrer,
55
+ mode: request.mode,
56
+ cache: request.cache,
57
+ redirect: request.redirect,
58
+ integrity: request.integrity,
59
+ isPrefetch: reqInfo.isPrefetch,
60
+ isRsc: reqInfo.isRsc,
61
+ isPageNavigation: reqInfo.isPageNavigation,
62
+ isStaticFile: reqInfo.isStaticFile,
63
+ isNextjsInternal: reqInfo.isNextjsInternal,
64
+ headers: debugHeaders
65
+ });
66
+ }
52
67
  if (pathname === eventEndpoint) {
53
68
  if (request.method === "POST") {
54
- return handleEventPost(request, config, dispatchEvent, updateEvent);
69
+ return (0, import_api_handler.handleEventPost)(request, config, dispatchEvent, updateEvent);
55
70
  }
56
71
  return Response.json({ error: "Method not allowed" }, { status: 405 });
57
72
  }
58
73
  if (reqInfo.isNextjsInternal || reqInfo.isPrefetch || reqInfo.isStaticFile) {
59
74
  return import_server.NextResponse.next();
60
75
  }
76
+ if (!reqInfo.isPageNavigation && !config.isApiPath(pathname)) {
77
+ return import_server.NextResponse.next();
78
+ }
61
79
  const pageRenderId = (0, import_uitils.generateId)();
62
80
  const serverContext = (0, import_uitils.createServerContext)(request);
63
81
  const response = import_server.NextResponse.next();
64
82
  const ctx = createRequestContext(request);
83
+ response.cookies.set(import_server_component_context.LAST_PAGE_RENDER_ID_COOKIE, pageRenderId, { path: "/" });
65
84
  const { anonId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config, response });
66
- const backends = resolveBackends(config, ctx);
67
- const templates = collectTemplates(backends);
68
- let scripts = [];
69
- if (config.pageViewMode !== "client-init") {
70
- if (config.excludePaths?.(pathname)) {
71
- (0, import_server_component_context.serializeServerComponentContext)(response, {
72
- pageRenderId,
73
- pathname: request.nextUrl.pathname,
74
- search: request.nextUrl.search,
75
- scripts,
76
- templates
77
- });
78
- return response;
79
- }
80
- const isApiPath = config.isApiPath(pathname);
81
- if (isApiPath && config.excludeApiCalls) {
82
- (0, import_server_component_context.serializeServerComponentContext)(response, {
83
- pageRenderId,
84
- pathname: request.nextUrl.pathname,
85
- search: request.nextUrl.search,
86
- scripts,
87
- templates
88
- });
89
- return response;
90
- }
91
- const userContext = await getUserContext(config, ctx);
92
- const pageViewEvent = createPageViewEvent(
85
+ if (config.excludePaths?.(pathname)) {
86
+ (0, import_server_component_context.serializeServerComponentContext)(response, {
87
+ pageRenderId,
88
+ pathname: request.nextUrl.pathname,
89
+ search: request.nextUrl.search,
90
+ scripts: []
91
+ });
92
+ return response;
93
+ }
94
+ const isApiPath = config.isApiPath(pathname);
95
+ if (isApiPath && config.excludeApiCalls) {
96
+ (0, import_server_component_context.serializeServerComponentContext)(response, {
93
97
  pageRenderId,
94
- serverContext,
95
- isApiPath,
96
- userContext,
97
- anonId
98
- );
99
- const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx);
100
- const actions = await clientActions;
101
- scripts = actions.items.filter(
102
- (i) => i.type === "script-template"
103
- );
104
- (0, import_server.after)(() => completion);
98
+ pathname: request.nextUrl.pathname,
99
+ search: request.nextUrl.search,
100
+ scripts: []
101
+ });
102
+ return response;
105
103
  }
104
+ const userContext = await (0, import_api_handler.getUserContext)(config, ctx);
105
+ const pageViewEvent = createPageViewEvent(
106
+ pageRenderId,
107
+ serverContext,
108
+ isApiPath,
109
+ userContext,
110
+ anonId
111
+ );
112
+ const { clientActions, completion } = dispatchEvent(pageViewEvent, ctx, "on-request");
113
+ const actions = await clientActions;
114
+ const scripts = actions.items.filter(
115
+ (i) => i.type === "script-template"
116
+ );
117
+ (0, import_server.after)(() => completion);
106
118
  (0, import_server_component_context.serializeServerComponentContext)(response, {
107
119
  pageRenderId,
108
120
  pathname: request.nextUrl.pathname,
109
121
  search: request.nextUrl.search,
110
- scripts,
111
- templates
122
+ scripts
112
123
  });
113
124
  return response;
114
125
  };
@@ -116,6 +127,7 @@ function createNextlyticsMiddleware(config, dispatchEvent, updateEvent) {
116
127
  function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext, anonymousUserId) {
117
128
  const eventType = isApiPath ? "apiCall" : "pageView";
118
129
  return {
130
+ origin: "server",
119
131
  collectedAt: serverContext.collectedAt.toISOString(),
120
132
  eventId: pageRenderId,
121
133
  type: eventType,
@@ -125,79 +137,6 @@ function createPageViewEvent(pageRenderId, serverContext, isApiPath, userContext
125
137
  properties: {}
126
138
  };
127
139
  }
128
- async function getUserContext(config, ctx) {
129
- if (!config.callbacks.getUser) return void 0;
130
- try {
131
- return await config.callbacks.getUser(ctx) || void 0;
132
- } catch {
133
- return void 0;
134
- }
135
- }
136
- async function handleEventPost(request, config, dispatchEvent, updateEvent) {
137
- const pageRenderId = request.headers.get(import_server_component_context.headers.pageRenderId);
138
- if (!pageRenderId) {
139
- return Response.json({ error: "Missing page render ID" }, { status: 400 });
140
- }
141
- let body;
142
- try {
143
- body = await request.json();
144
- } catch {
145
- return Response.json({ error: "Invalid JSON" }, { status: 400 });
146
- }
147
- const { type, payload } = body;
148
- const ctx = createRequestContext(request);
149
- const serverContext = (0, import_uitils.createServerContext)(request);
150
- const userContext = await getUserContext(config, ctx);
151
- const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config });
152
- if (type === "client-init") {
153
- const clientContext = payload;
154
- if (clientContext?.path) {
155
- serverContext.path = clientContext.path;
156
- }
157
- if (config.pageViewMode === "client-init") {
158
- const event = {
159
- eventId: pageRenderId,
160
- type: "pageView",
161
- collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
162
- anonymousUserId,
163
- serverContext,
164
- clientContext,
165
- userContext,
166
- properties: {}
167
- };
168
- const { clientActions, completion } = dispatchEvent(event, ctx);
169
- const actions = await clientActions;
170
- (0, import_server.after)(() => completion);
171
- const scripts = actions.items.filter((i) => i.type === "script-template");
172
- return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
173
- } else {
174
- (0, import_server.after)(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx));
175
- return Response.json({ ok: true });
176
- }
177
- } else if (type === "client-event") {
178
- const clientContext = payload.clientContext || void 0;
179
- if (clientContext?.path) {
180
- serverContext.path = clientContext.path;
181
- }
182
- const event = {
183
- eventId: (0, import_uitils.generateId)(),
184
- parentEventId: pageRenderId,
185
- type: payload.name || type,
186
- collectedAt: payload.collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
187
- anonymousUserId,
188
- serverContext,
189
- clientContext,
190
- userContext,
191
- properties: payload.props || {}
192
- };
193
- const { clientActions, completion } = dispatchEvent(event, ctx);
194
- const actions = await clientActions;
195
- (0, import_server.after)(() => completion);
196
- const scripts = actions.items.filter((i) => i.type === "script-template");
197
- return Response.json({ ok: true, scripts: scripts.length > 0 ? scripts : void 0 });
198
- }
199
- return Response.json({ ok: true });
200
- }
201
140
  // Annotate the CommonJS export names for ESM import in node:
202
141
  0 && (module.exports = {
203
142
  createNextlyticsMiddleware
@@ -5,41 +5,16 @@ import './types.js';
5
5
  import 'next/dist/server/web/spec-extension/cookies';
6
6
  import 'next/server';
7
7
 
8
- type ContextWithHeaders = {
8
+ type PagesRouterContext = {
9
9
  req: {
10
10
  headers: Record<string, string | string[] | undefined>;
11
+ cookies?: Record<string, string>;
11
12
  };
12
13
  };
13
14
  /**
14
- * Extract Nextlytics context from Pages Router context (getServerSideProps or getInitialProps).
15
- * Use this in _app.tsx with getInitialProps to pass context to NextlyticsClient.
16
- *
17
- * @example
18
- * ```tsx
19
- * // pages/_app.tsx
20
- * import type { AppContext, AppProps } from 'next/app'
21
- * import { NextlyticsClient, getNextlyticsProps, type NextlyticsContext } from '@nextlytics/core'
22
- *
23
- * type MyAppProps = AppProps & { nextlyticsCtx: NextlyticsContext }
24
- *
25
- * function MyApp({ Component, pageProps, nextlyticsCtx }: MyAppProps) {
26
- * return (
27
- * <NextlyticsClient ctx={nextlyticsCtx}>
28
- * <Component {...pageProps} />
29
- * </NextlyticsClient>
30
- * )
31
- * }
32
- *
33
- * MyApp.getInitialProps = async (appContext: AppContext) => {
34
- * return {
35
- * pageProps: appContext.Component.getInitialProps
36
- * ? await appContext.Component.getInitialProps(appContext.ctx)
37
- * : {},
38
- * nextlyticsCtx: getNextlyticsProps(appContext.ctx),
39
- * }
40
- * }
41
- * ```
15
+ * Get Nextlytics props for Pages Router _app.tsx.
16
+ * Reads context from headers set by middleware.
42
17
  */
43
- declare function getNextlyticsProps(ctx: ContextWithHeaders): NextlyticsContext;
18
+ declare function getNextlyticsProps(ctx: PagesRouterContext): NextlyticsContext;
44
19
 
45
- export { getNextlyticsProps };
20
+ export { type PagesRouterContext, getNextlyticsProps };
@@ -35,8 +35,7 @@ function getNextlyticsProps(ctx) {
35
35
  }
36
36
  return {
37
37
  requestId: context.pageRenderId,
38
- scripts: context.scripts,
39
- templates: context.templates
38
+ scripts: context.scripts
40
39
  };
41
40
  }
42
41
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,14 +1,15 @@
1
1
  import { NextResponse } from 'next/server';
2
- import { TemplatizedScriptInsertion, JavascriptTemplate } from './types.js';
2
+ import { TemplatizedScriptInsertion } from './types.js';
3
3
  import 'next/dist/server/web/spec-extension/cookies';
4
4
 
5
- declare const headerKeys: {
6
- readonly pathname: "x-nc-pathname";
7
- readonly search: "x-nc-search";
8
- readonly pageRenderId: "x-nc-page-render-id";
9
- readonly scripts: "x-nc-scripts";
10
- readonly templates: "x-nc-templates";
5
+ declare const headerNames: {
6
+ readonly pathname: "x-nl-pathname";
7
+ readonly search: "x-nl-search";
8
+ readonly pageRenderId: "x-nl-page-render-id";
9
+ readonly isSoftNavigation: "x-nl-is-soft-nav";
10
+ readonly scripts: "x-nl-scripts";
11
11
  };
12
+ declare const LAST_PAGE_RENDER_ID_COOKIE = "last-page-render-id";
12
13
  /** Context passed from middleware to server components via headers */
13
14
  type ServerComponentContext = {
14
15
  /** Unique page render ID (event ID) */
@@ -17,14 +18,12 @@ type ServerComponentContext = {
17
18
  pathname: string;
18
19
  /** Query string */
19
20
  search: string;
20
- /** Script actions to execute on client */
21
+ /** Script actions to execute on client (params only, templates come from config) */
21
22
  scripts: TemplatizedScriptInsertion<unknown>[];
22
- /** Template definitions for scripts */
23
- templates: Record<string, JavascriptTemplate>;
24
23
  };
25
24
  /** Serialize context to response headers (called in middleware) */
26
25
  declare function serializeServerComponentContext(response: NextResponse, ctx: ServerComponentContext): void;
27
26
  /** Restore context from request headers (called in server components) */
28
27
  declare function restoreServerComponentContext(headersList: Headers): ServerComponentContext | null;
29
28
 
30
- export { type ServerComponentContext, headerKeys as headers, restoreServerComponentContext, serializeServerComponentContext };
29
+ export { LAST_PAGE_RENDER_ID_COOKIE, type ServerComponentContext, headerNames, restoreServerComponentContext, serializeServerComponentContext };
@@ -18,32 +18,31 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var server_component_context_exports = {};
20
20
  __export(server_component_context_exports, {
21
- headers: () => headerKeys,
21
+ LAST_PAGE_RENDER_ID_COOKIE: () => LAST_PAGE_RENDER_ID_COOKIE,
22
+ headerNames: () => headerNames,
22
23
  restoreServerComponentContext: () => restoreServerComponentContext,
23
24
  serializeServerComponentContext: () => serializeServerComponentContext
24
25
  });
25
26
  module.exports = __toCommonJS(server_component_context_exports);
26
- const HEADER_PREFIX = "x-nc-";
27
- const headerKeys = {
27
+ const HEADER_PREFIX = "x-nl-";
28
+ const headerNames = {
28
29
  pathname: `${HEADER_PREFIX}pathname`,
29
30
  search: `${HEADER_PREFIX}search`,
30
31
  pageRenderId: `${HEADER_PREFIX}page-render-id`,
31
- scripts: `${HEADER_PREFIX}scripts`,
32
- templates: `${HEADER_PREFIX}templates`
32
+ isSoftNavigation: `${HEADER_PREFIX}is-soft-nav`,
33
+ scripts: `${HEADER_PREFIX}scripts`
33
34
  };
35
+ const LAST_PAGE_RENDER_ID_COOKIE = "last-page-render-id";
34
36
  function serializeServerComponentContext(response, ctx) {
35
- response.headers.set(headerKeys.pageRenderId, ctx.pageRenderId);
36
- response.headers.set(headerKeys.pathname, ctx.pathname);
37
- response.headers.set(headerKeys.search, ctx.search);
37
+ response.headers.set(headerNames.pageRenderId, ctx.pageRenderId);
38
+ response.headers.set(headerNames.pathname, ctx.pathname);
39
+ response.headers.set(headerNames.search, ctx.search);
38
40
  if (ctx.scripts.length > 0) {
39
41
  const scriptParts = ctx.scripts.filter((item) => item.type === "script-template").map((s) => `${s.templateId}=${JSON.stringify(s.params)}`);
40
42
  if (scriptParts.length > 0) {
41
- response.headers.set(headerKeys.scripts, scriptParts.join(";"));
43
+ response.headers.set(headerNames.scripts, scriptParts.join(";"));
42
44
  }
43
45
  }
44
- if (Object.keys(ctx.templates).length > 0) {
45
- response.headers.set(headerKeys.templates, JSON.stringify(ctx.templates));
46
- }
47
46
  }
48
47
  function parseScriptsHeader(header) {
49
48
  const scripts = [];
@@ -62,34 +61,25 @@ function parseScriptsHeader(header) {
62
61
  return scripts;
63
62
  }
64
63
  function restoreServerComponentContext(headersList) {
65
- const pageRenderId = headersList.get(headerKeys.pageRenderId);
64
+ const pageRenderId = headersList.get(headerNames.pageRenderId);
66
65
  if (!pageRenderId) {
67
66
  return null;
68
67
  }
69
- const pathname = headersList.get(headerKeys.pathname) || "";
70
- const search = headersList.get(headerKeys.search) || "";
71
- const scriptsHeader = headersList.get(headerKeys.scripts);
68
+ const pathname = headersList.get(headerNames.pathname) || "";
69
+ const search = headersList.get(headerNames.search) || "";
70
+ const scriptsHeader = headersList.get(headerNames.scripts);
72
71
  const scripts = scriptsHeader ? parseScriptsHeader(scriptsHeader) : [];
73
- let templates = {};
74
- const templatesHeader = headersList.get(headerKeys.templates);
75
- if (templatesHeader) {
76
- try {
77
- templates = JSON.parse(templatesHeader);
78
- } catch {
79
- console.warn("[Nextlytics] Failed to parse templates header");
80
- }
81
- }
82
72
  return {
83
73
  pageRenderId,
84
74
  pathname,
85
75
  search,
86
- scripts,
87
- templates
76
+ scripts
88
77
  };
89
78
  }
90
79
  // Annotate the CommonJS export names for ESM import in node:
91
80
  0 && (module.exports = {
92
- headers,
81
+ LAST_PAGE_RENDER_ID_COOKIE,
82
+ headerNames,
93
83
  restoreServerComponentContext,
94
84
  serializeServerComponentContext
95
85
  });
package/dist/server.d.ts CHANGED
@@ -1,13 +1,8 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
1
  import { NextlyticsConfig, NextlyticsResult, RequestContext } from './types.js';
4
2
  import 'next/dist/server/web/spec-extension/cookies';
5
3
  import 'next/server';
6
4
 
7
5
  declare function createRequestContext(): Promise<RequestContext>;
8
- declare function NextlyticsServer({ children }: {
9
- children: ReactNode;
10
- }): Promise<react_jsx_runtime.JSX.Element>;
11
6
  declare function Nextlytics(userConfig: NextlyticsConfig): NextlyticsResult;
12
7
 
13
- export { Nextlytics, NextlyticsServer, createRequestContext };
8
+ export { Nextlytics, createRequestContext };
package/dist/server.js CHANGED
@@ -19,7 +19,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var server_exports = {};
20
20
  __export(server_exports, {
21
21
  Nextlytics: () => Nextlytics,
22
- NextlyticsServer: () => NextlyticsServer,
23
22
  createRequestContext: () => createRequestContext
24
23
  });
25
24
  module.exports = __toCommonJS(server_exports);
@@ -29,18 +28,28 @@ var import_headers2 = require("./headers");
29
28
  var import_server_component_context = require("./server-component-context");
30
29
  var import_anonymous_user = require("./anonymous-user");
31
30
  var import_client = require("./client");
32
- var import_handlers = require("./handlers");
33
31
  var import_config_helpers = require("./config-helpers");
34
32
  var import_middleware = require("./middleware");
35
33
  var import_uitils = require("./uitils");
36
- function resolveBackends(config, ctx) {
37
- const backends = config.backends || [];
38
- return backends.map((backend) => {
39
- if (typeof backend === "function") {
40
- return backend(ctx);
34
+ function isBackendWithConfig(entry) {
35
+ return typeof entry === "object" && entry !== null && "backend" in entry;
36
+ }
37
+ function resolveBackends(config, ctx, policyFilter) {
38
+ const entries = config.backends || [];
39
+ return entries.map((entry) => {
40
+ if (isBackendWithConfig(entry)) {
41
+ const backend2 = typeof entry.backend === "function" ? entry.backend(ctx) : entry.backend;
42
+ return backend2 ? { backend: backend2, pageViewDelivery: entry.pageViewDelivery ?? "on-request" } : null;
43
+ }
44
+ const backend = typeof entry === "function" ? entry(ctx) : entry;
45
+ return backend ? { backend, pageViewDelivery: "on-request" } : null;
46
+ }).filter((b) => b !== null).filter((b) => {
47
+ if (!policyFilter) return true;
48
+ if (policyFilter === "client-actions") {
49
+ return b.pageViewDelivery === "on-page-load" || b.backend.returnsClientActions;
41
50
  }
42
- return backend;
43
- }).filter((b) => b !== null);
51
+ return b.pageViewDelivery === policyFilter;
52
+ });
44
53
  }
45
54
  function resolvePlugins(config, ctx) {
46
55
  const plugins = config.plugins || [];
@@ -70,24 +79,23 @@ async function createRequestContext() {
70
79
  headers: _headers
71
80
  };
72
81
  }
73
- async function NextlyticsServer({ children }) {
74
- const headersList = await (0, import_headers.headers)();
75
- const ctx = (0, import_server_component_context.restoreServerComponentContext)(headersList);
76
- if (!ctx) {
77
- console.warn(
78
- "[Nextlytics] nextlyticsMiddleware should be added in order for NextlyticsServer to work"
79
- );
80
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
82
+ function collectTemplates(config, ctx) {
83
+ const templates = {};
84
+ const backends = resolveBackends(config, ctx);
85
+ for (const { backend } of backends) {
86
+ if (backend.getClientSideTemplates) {
87
+ Object.assign(templates, backend.getClientSideTemplates());
88
+ }
81
89
  }
82
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client.NextlyticsClient, { requestId: ctx.pageRenderId, scripts: ctx.scripts, templates: ctx.templates, children });
90
+ return templates;
83
91
  }
84
92
  function Nextlytics(userConfig) {
85
93
  const config = (0, import_config_helpers.withDefaults)(userConfig);
86
94
  const validationResult = (0, import_config_helpers.validateConfig)(config);
87
95
  (0, import_config_helpers.logConfigWarnings)(validationResult);
88
- const dispatchEventInternal = (event, ctx) => {
96
+ const dispatchEventInternal = (event, ctx, policyFilter) => {
89
97
  const plugins = resolvePlugins(config, ctx);
90
- const backends = resolveBackends(config, ctx);
98
+ const resolved = resolveBackends(config, ctx, policyFilter);
91
99
  const pluginsDone = (async () => {
92
100
  for (const plugin of plugins) {
93
101
  try {
@@ -98,7 +106,7 @@ function Nextlytics(userConfig) {
98
106
  }
99
107
  })();
100
108
  const backendResults = pluginsDone.then(() => {
101
- return backends.map((backend) => {
109
+ return resolved.map(({ backend }) => {
102
110
  const start = Date.now();
103
111
  const promise = backend.onEvent(event).then((result) => ({ ok: true, ms: Date.now() - start, result })).catch((err) => {
104
112
  console.error(`[Nextlytics] Backend "${backend.name}" failed on onEvent:`, err);
@@ -116,7 +124,7 @@ function Nextlytics(userConfig) {
116
124
  const completion = backendResults.then(async (results) => {
117
125
  const settled = await Promise.all(results.map((r) => r.promise));
118
126
  if (config.debug) {
119
- const nameWidth = Math.max(...results.map((r) => r.backend.name.length));
127
+ const nameWidth = Math.max(...results.map((r) => r.backend.name.length), 1);
120
128
  console.log(
121
129
  `[Nextlytics] dispatchEvent ${event.type} ${event.eventId} (${results.length} backends)`
122
130
  );
@@ -131,9 +139,11 @@ function Nextlytics(userConfig) {
131
139
  return { clientActions, completion };
132
140
  };
133
141
  const updateEventInternal = async (eventId, patch, ctx) => {
134
- const backends = resolveBackends(config, ctx).filter((backend) => backend.supportsUpdates);
142
+ const resolved = resolveBackends(config, ctx, "on-request").filter(
143
+ ({ backend }) => backend.supportsUpdates
144
+ );
135
145
  const results = await Promise.all(
136
- backends.map(async (backend) => {
146
+ resolved.map(async ({ backend }) => {
137
147
  const start = Date.now();
138
148
  try {
139
149
  await backend.updateEvent(eventId, patch);
@@ -145,9 +155,9 @@ function Nextlytics(userConfig) {
145
155
  }
146
156
  })
147
157
  );
148
- if (config.debug && backends.length > 0) {
149
- const nameWidth = Math.max(...backends.map((b) => b.name.length));
150
- console.log(`[Nextlytics] updateEvent ${eventId} (${backends.length} backends)`);
158
+ if (config.debug && resolved.length > 0) {
159
+ const nameWidth = Math.max(...resolved.map(({ backend }) => backend.name.length));
160
+ console.log(`[Nextlytics] updateEvent ${eventId} (${resolved.length} backends)`);
151
161
  results.forEach((r) => {
152
162
  const status = r.ok ? "ok" : "fail";
153
163
  console.log(` ${r.backend.name.padEnd(nameWidth)} ${status.padEnd(4)} ${r.ms}ms`);
@@ -163,11 +173,21 @@ function Nextlytics(userConfig) {
163
173
  return updateEventInternal(eventId, patch, ctx);
164
174
  };
165
175
  const middleware = (0, import_middleware.createNextlyticsMiddleware)(config, dispatchEventInternal, updateEventInternal);
166
- const handlers = (0, import_handlers.createHandlers)(config, dispatchEventInternal, updateEventInternal);
176
+ async function Server({ children }) {
177
+ const headersList = await (0, import_headers.headers)();
178
+ const ctx = (0, import_server_component_context.restoreServerComponentContext)(headersList);
179
+ if (!ctx) {
180
+ console.warn("[Nextlytics] nextlyticsMiddleware should be added in order for Server to work");
181
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
182
+ }
183
+ const requestCtx = await createRequestContext();
184
+ const templates = collectTemplates(config, requestCtx);
185
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client.NextlyticsClient, { ctx: { requestId: ctx.pageRenderId, scripts: ctx.scripts, templates }, children });
186
+ }
167
187
  const analytics = async () => {
168
188
  const headersList = await (0, import_headers.headers)();
169
189
  const cookieStore = await (0, import_headers.cookies)();
170
- const pageRenderId = headersList.get(import_server_component_context.headers.pageRenderId);
190
+ const pageRenderId = headersList.get(import_server_component_context.headerNames.pageRenderId);
171
191
  const serverContext = createServerContextFromHeaders(headersList);
172
192
  const ctx = { headers: headersList, cookies: cookieStore };
173
193
  const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({ ctx, serverContext, config });
@@ -185,6 +205,7 @@ function Nextlytics(userConfig) {
185
205
  return { ok: false };
186
206
  }
187
207
  const event = {
208
+ origin: "server",
188
209
  eventId: (0, import_uitils.generateId)(),
189
210
  parentEventId: pageRenderId,
190
211
  type: eventName,
@@ -199,7 +220,13 @@ function Nextlytics(userConfig) {
199
220
  }
200
221
  };
201
222
  };
202
- return { middleware, handlers, analytics, dispatchEvent, updateEvent };
223
+ return {
224
+ middleware,
225
+ analytics,
226
+ dispatchEvent,
227
+ updateEvent,
228
+ NextlyticsServer: Server
229
+ };
203
230
  }
204
231
  function createServerContextFromHeaders(headersList) {
205
232
  const rawHeaders = {};
@@ -207,8 +234,8 @@ function createServerContextFromHeaders(headersList) {
207
234
  rawHeaders[key] = value;
208
235
  });
209
236
  const requestHeaders = (0, import_headers2.removeSensitiveHeaders)(rawHeaders);
210
- const pathname = headersList.get(import_server_component_context.headers.pathname) || "";
211
- const search = headersList.get(import_server_component_context.headers.search) || "";
237
+ const pathname = headersList.get(import_server_component_context.headerNames.pathname) || "";
238
+ const search = headersList.get(import_server_component_context.headerNames.search) || "";
212
239
  const searchParams = {};
213
240
  if (search) {
214
241
  const params = new URLSearchParams(search);
@@ -233,6 +260,5 @@ function createServerContextFromHeaders(headersList) {
233
260
  // Annotate the CommonJS export names for ESM import in node:
234
261
  0 && (module.exports = {
235
262
  Nextlytics,
236
- NextlyticsServer,
237
263
  createRequestContext
238
264
  });
@@ -0,0 +1,6 @@
1
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
4
+ declare function stableHash(value: JsonValue): string;
5
+
6
+ export { type JsonValue, stableHash };