@nextlytics/core 0.3.0 → 0.3.1-canary.92
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/api-handler.d.ts +11 -0
- package/dist/api-handler.js +182 -0
- package/dist/backends/ga.d.ts +5 -0
- package/dist/backends/ga.js +93 -17
- package/dist/backends/gtm.js +25 -8
- package/dist/backends/lib/db.d.ts +29 -29
- package/dist/backends/lib/db.js +17 -17
- package/dist/backends/logging.js +33 -0
- package/dist/backends/neon.js +11 -4
- package/dist/backends/segment.js +1 -0
- package/dist/client-utils.d.ts +35 -0
- package/dist/client-utils.js +121 -0
- package/dist/client.d.ts +1 -4
- package/dist/client.js +141 -67
- package/dist/config-helpers.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -3
- package/dist/middleware.d.ts +2 -3
- package/dist/middleware.js +39 -127
- package/dist/pages-router.d.ts +6 -31
- package/dist/pages-router.js +1 -2
- package/dist/plugins/vercel-geo.d.ts +1 -1
- package/dist/server-component-context.d.ts +10 -11
- package/dist/server-component-context.js +18 -28
- package/dist/server.d.ts +1 -6
- package/dist/server.js +40 -21
- package/dist/stable-hash.d.ts +6 -0
- package/dist/stable-hash.js +76 -0
- package/dist/types.d.ts +61 -18
- package/dist/uitils.d.ts +7 -1
- package/dist/uitils.js +39 -5
- package/package.json +1 -1
- package/dist/handlers.d.ts +0 -12
- package/dist/handlers.js +0 -40
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { NextlyticsEvent, RequestContext, PageViewDelivery, DispatchResult, UserContext } from './types.js';
|
|
3
|
+
import { NextlyticsConfigWithDefaults } from './config-helpers.js';
|
|
4
|
+
import 'next/dist/server/web/spec-extension/cookies';
|
|
5
|
+
|
|
6
|
+
type DispatchEvent = (event: NextlyticsEvent, ctx: RequestContext, policyFilter?: PageViewDelivery | "client-actions") => DispatchResult;
|
|
7
|
+
type UpdateEvent = (eventId: string, patch: Partial<NextlyticsEvent>, ctx: RequestContext) => Promise<void>;
|
|
8
|
+
declare function getUserContext(config: NextlyticsConfigWithDefaults, ctx: RequestContext): Promise<UserContext | undefined>;
|
|
9
|
+
declare function handleEventPost(request: NextRequest, config: NextlyticsConfigWithDefaults, dispatchEvent: DispatchEvent, updateEvent: UpdateEvent): Promise<Response>;
|
|
10
|
+
|
|
11
|
+
export { type DispatchEvent, type UpdateEvent, getUserContext, handleEventPost };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var api_handler_exports = {};
|
|
20
|
+
__export(api_handler_exports, {
|
|
21
|
+
getUserContext: () => getUserContext,
|
|
22
|
+
handleEventPost: () => handleEventPost
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(api_handler_exports);
|
|
25
|
+
var import_server = require("next/server");
|
|
26
|
+
var import_server_component_context = require("./server-component-context");
|
|
27
|
+
var import_uitils = require("./uitils");
|
|
28
|
+
var import_anonymous_user = require("./anonymous-user");
|
|
29
|
+
function createRequestContext(request) {
|
|
30
|
+
return {
|
|
31
|
+
headers: request.headers,
|
|
32
|
+
cookies: request.cookies
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function getUserContext(config, ctx) {
|
|
36
|
+
if (!config.callbacks.getUser) return void 0;
|
|
37
|
+
try {
|
|
38
|
+
return await config.callbacks.getUser(ctx) || void 0;
|
|
39
|
+
} catch {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function reconstructServerContext(apiCallContext, clientContext) {
|
|
44
|
+
const searchParams = {};
|
|
45
|
+
if (clientContext.search) {
|
|
46
|
+
const params = new URLSearchParams(clientContext.search);
|
|
47
|
+
params.forEach((value, key) => {
|
|
48
|
+
if (!searchParams[key]) searchParams[key] = [];
|
|
49
|
+
searchParams[key].push(value);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
...apiCallContext,
|
|
54
|
+
host: clientContext.host || apiCallContext.host,
|
|
55
|
+
path: clientContext.path || apiCallContext.path,
|
|
56
|
+
search: Object.keys(searchParams).length > 0 ? searchParams : apiCallContext.search,
|
|
57
|
+
method: "GET"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function filterScripts(actions) {
|
|
61
|
+
const scripts = actions.items.filter((i) => i.type === "script-template");
|
|
62
|
+
return scripts.length > 0 ? scripts : void 0;
|
|
63
|
+
}
|
|
64
|
+
async function handleClientInit(request, hctx) {
|
|
65
|
+
const {
|
|
66
|
+
pageRenderId,
|
|
67
|
+
ctx,
|
|
68
|
+
apiCallServerContext,
|
|
69
|
+
userContext,
|
|
70
|
+
config,
|
|
71
|
+
dispatchEvent,
|
|
72
|
+
updateEvent
|
|
73
|
+
} = hctx;
|
|
74
|
+
const { clientContext } = request;
|
|
75
|
+
const serverContext = reconstructServerContext(apiCallServerContext, clientContext);
|
|
76
|
+
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
|
|
77
|
+
ctx,
|
|
78
|
+
serverContext,
|
|
79
|
+
config
|
|
80
|
+
});
|
|
81
|
+
const isSoftNavigation = hctx.isSoftNavigation;
|
|
82
|
+
const eventId = isSoftNavigation ? (0, import_uitils.generateId)() : pageRenderId;
|
|
83
|
+
const event = {
|
|
84
|
+
origin: "client",
|
|
85
|
+
eventId,
|
|
86
|
+
parentEventId: isSoftNavigation ? pageRenderId : void 0,
|
|
87
|
+
type: "pageView",
|
|
88
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
89
|
+
anonymousUserId,
|
|
90
|
+
serverContext,
|
|
91
|
+
clientContext,
|
|
92
|
+
userContext,
|
|
93
|
+
properties: {}
|
|
94
|
+
};
|
|
95
|
+
if (isSoftNavigation) {
|
|
96
|
+
const { clientActions, completion: completion2 } = dispatchEvent(event, ctx);
|
|
97
|
+
const actions = await clientActions;
|
|
98
|
+
(0, import_server.after)(() => completion2);
|
|
99
|
+
return Response.json({
|
|
100
|
+
ok: true,
|
|
101
|
+
items: filterScripts(actions)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const { completion } = dispatchEvent(event, ctx, "client-actions");
|
|
105
|
+
(0, import_server.after)(() => completion);
|
|
106
|
+
(0, import_server.after)(() => updateEvent(pageRenderId, { clientContext, userContext, anonymousUserId }, ctx));
|
|
107
|
+
return Response.json({ ok: true });
|
|
108
|
+
}
|
|
109
|
+
async function handleClientEvent(request, hctx) {
|
|
110
|
+
const { pageRenderId, ctx, apiCallServerContext, userContext, config, dispatchEvent } = hctx;
|
|
111
|
+
const { clientContext, name, props, collectedAt } = request;
|
|
112
|
+
const serverContext = clientContext ? reconstructServerContext(apiCallServerContext, clientContext) : apiCallServerContext;
|
|
113
|
+
const { anonId: anonymousUserId } = await (0, import_anonymous_user.resolveAnonymousUser)({
|
|
114
|
+
ctx,
|
|
115
|
+
serverContext,
|
|
116
|
+
config
|
|
117
|
+
});
|
|
118
|
+
const event = {
|
|
119
|
+
origin: "client",
|
|
120
|
+
eventId: (0, import_uitils.generateId)(),
|
|
121
|
+
parentEventId: pageRenderId,
|
|
122
|
+
type: name,
|
|
123
|
+
collectedAt: collectedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
124
|
+
anonymousUserId,
|
|
125
|
+
serverContext,
|
|
126
|
+
clientContext,
|
|
127
|
+
userContext,
|
|
128
|
+
properties: props || {}
|
|
129
|
+
};
|
|
130
|
+
const { clientActions, completion } = dispatchEvent(event, ctx);
|
|
131
|
+
const actions = await clientActions;
|
|
132
|
+
(0, import_server.after)(() => completion);
|
|
133
|
+
return Response.json({ ok: true, items: filterScripts(actions) });
|
|
134
|
+
}
|
|
135
|
+
async function handleEventPost(request, config, dispatchEvent, updateEvent) {
|
|
136
|
+
const softNavHeader = request.headers.get(import_server_component_context.headerNames.isSoftNavigation);
|
|
137
|
+
const isSoftNavigation = softNavHeader === "1";
|
|
138
|
+
const pageRenderIdHeader = request.headers.get(import_server_component_context.headerNames.pageRenderId);
|
|
139
|
+
if (!pageRenderIdHeader) {
|
|
140
|
+
return Response.json({ error: "Missing page render ID" }, { status: 400 });
|
|
141
|
+
}
|
|
142
|
+
let body;
|
|
143
|
+
try {
|
|
144
|
+
body = await request.json();
|
|
145
|
+
} catch {
|
|
146
|
+
return Response.json({ error: "Invalid JSON" }, { status: 400 });
|
|
147
|
+
}
|
|
148
|
+
const ctx = createRequestContext(request);
|
|
149
|
+
const apiCallServerContext = (0, import_uitils.createServerContext)(request);
|
|
150
|
+
const userContext = await getUserContext(config, ctx);
|
|
151
|
+
const cookiePageRenderId = request.cookies.get(import_server_component_context.LAST_PAGE_RENDER_ID_COOKIE)?.value;
|
|
152
|
+
const pageRenderId = isSoftNavigation ? cookiePageRenderId ?? (0, import_uitils.generateId)() : pageRenderIdHeader;
|
|
153
|
+
if (isSoftNavigation && !cookiePageRenderId && config.debug) {
|
|
154
|
+
console.warn(
|
|
155
|
+
"[Nextlytics] Missing last-page-render-id cookie on soft navigation; using a new id."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const hctx = {
|
|
159
|
+
pageRenderId,
|
|
160
|
+
isSoftNavigation,
|
|
161
|
+
ctx,
|
|
162
|
+
apiCallServerContext,
|
|
163
|
+
userContext,
|
|
164
|
+
config,
|
|
165
|
+
dispatchEvent,
|
|
166
|
+
updateEvent
|
|
167
|
+
};
|
|
168
|
+
const bodyType = body.type;
|
|
169
|
+
switch (bodyType) {
|
|
170
|
+
case "page-view":
|
|
171
|
+
return handleClientInit(body, hctx);
|
|
172
|
+
case "custom-event":
|
|
173
|
+
return handleClientEvent(body, hctx);
|
|
174
|
+
default:
|
|
175
|
+
return Response.json({ ok: false, error: `Unknown body type ${bodyType}` }, { status: 400 });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
getUserContext,
|
|
181
|
+
handleEventPost
|
|
182
|
+
});
|
package/dist/backends/ga.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ type GoogleAnalyticsBackendOptions = {
|
|
|
15
15
|
* - "anonymousUserId": Always use Nextlytics anonymousUserId
|
|
16
16
|
*/
|
|
17
17
|
clientIdSource?: "gaCookie" | "anonymousUserId";
|
|
18
|
+
/**
|
|
19
|
+
* Prefer sending client-origin events from the browser (gtag) instead of Measurement Protocol.
|
|
20
|
+
* Default: true. Set to false to force Measurement Protocol when apiSecret is provided.
|
|
21
|
+
*/
|
|
22
|
+
preferClientSideForClientEvents?: boolean;
|
|
18
23
|
};
|
|
19
24
|
declare function googleAnalyticsBackend(opts: GoogleAnalyticsBackendOptions): NextlyticsBackendFactory;
|
|
20
25
|
|
package/dist/backends/ga.js
CHANGED
|
@@ -21,7 +21,9 @@ __export(ga_exports, {
|
|
|
21
21
|
googleAnalyticsBackend: () => googleAnalyticsBackend
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(ga_exports);
|
|
24
|
-
const
|
|
24
|
+
const GA_INIT_TEMPLATE = "ga-gtag";
|
|
25
|
+
const GA_PROPERTIES_TEMPLATE = "ga-properties";
|
|
26
|
+
const GA_EVENT_TEMPLATE = "ga-event";
|
|
25
27
|
function parseGaCookie(cookieValue) {
|
|
26
28
|
const match = cookieValue.match(/^GA\d+\.\d+\.(.+)$/);
|
|
27
29
|
return match ? match[1] : null;
|
|
@@ -118,27 +120,51 @@ function googleAnalyticsBackend(opts) {
|
|
|
118
120
|
return (ctx) => {
|
|
119
121
|
const gaCookie = ctx.cookies.get("_ga");
|
|
120
122
|
const gaCookieClientId = gaCookie ? parseGaCookie(gaCookie.value) : null;
|
|
123
|
+
const preferClientSideForClientEvents = opts.preferClientSideForClientEvents ?? true;
|
|
121
124
|
return {
|
|
122
125
|
name: "google-analytics",
|
|
123
126
|
returnsClientActions: true,
|
|
124
127
|
supportsUpdates: false,
|
|
125
128
|
getClientSideTemplates() {
|
|
126
129
|
return {
|
|
127
|
-
[
|
|
130
|
+
[GA_EVENT_TEMPLATE]: {
|
|
131
|
+
deps: "{{eventId}}",
|
|
128
132
|
items: [
|
|
133
|
+
// Update user properties for this event (if provided)
|
|
134
|
+
{
|
|
135
|
+
body: [
|
|
136
|
+
"gtag('set', {{json(properties)}});",
|
|
137
|
+
"gtag('event', '{{eventName}}', {{json(eventParams)}});"
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
[GA_INIT_TEMPLATE]: {
|
|
143
|
+
deps: "{{measurementId}}{{json(initial_config)}}",
|
|
144
|
+
items: [
|
|
145
|
+
// External gtag.js - load once
|
|
129
146
|
{
|
|
130
|
-
async: "true",
|
|
131
147
|
src: "https://www.googletagmanager.com/gtag/js?id={{measurementId}}",
|
|
132
|
-
|
|
148
|
+
async: true
|
|
133
149
|
},
|
|
150
|
+
// gtag definition and initialization - run once
|
|
134
151
|
{
|
|
135
152
|
body: [
|
|
136
153
|
"window.dataLayer = window.dataLayer || [];",
|
|
137
|
-
"function gtag(){dataLayer.push(arguments);}",
|
|
154
|
+
opts.debugMode ? "function gtag(){ console.log('[gtag() call]', arguments); dataLayer.push(arguments); }" : "function gtag(){dataLayer.push(arguments);}",
|
|
155
|
+
"window.gtag = gtag;",
|
|
138
156
|
"gtag('js', new Date());",
|
|
139
|
-
"gtag('config', '{{measurementId}}', {{json(
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
"gtag('config', '{{measurementId}}', {{json(initial_config)}});"
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
[GA_PROPERTIES_TEMPLATE]: {
|
|
163
|
+
deps: "{{json(properties)}}",
|
|
164
|
+
items: [
|
|
165
|
+
// Updates that should NOT trigger page_view (e.g., user_id, user_properties)
|
|
166
|
+
{
|
|
167
|
+
body: "gtag('set', {{json(properties)}});"
|
|
142
168
|
}
|
|
143
169
|
]
|
|
144
170
|
}
|
|
@@ -155,33 +181,66 @@ function googleAnalyticsBackend(opts) {
|
|
|
155
181
|
} = event.userContext?.traits ?? {};
|
|
156
182
|
const userProperties = Object.keys(customTraits).length > 0 ? customTraits : void 0;
|
|
157
183
|
if (event.type === "pageView") {
|
|
158
|
-
const
|
|
159
|
-
|
|
184
|
+
const initial_config = {
|
|
185
|
+
// Rely on GA auto page_view (including SPA history changes).
|
|
186
|
+
send_page_view: true,
|
|
160
187
|
client_id: clientId
|
|
161
188
|
};
|
|
162
189
|
if (debugMode) {
|
|
163
|
-
|
|
190
|
+
initial_config.debug_mode = true;
|
|
164
191
|
}
|
|
192
|
+
const properties2 = {};
|
|
165
193
|
if (userId) {
|
|
166
|
-
|
|
194
|
+
properties2.user_id = userId;
|
|
167
195
|
}
|
|
168
196
|
if (userProperties) {
|
|
169
|
-
|
|
197
|
+
properties2.user_properties = userProperties;
|
|
170
198
|
}
|
|
171
199
|
return {
|
|
172
200
|
items: [
|
|
173
201
|
{
|
|
174
202
|
type: "script-template",
|
|
175
|
-
templateId:
|
|
203
|
+
templateId: GA_INIT_TEMPLATE,
|
|
176
204
|
params: {
|
|
177
205
|
measurementId,
|
|
178
|
-
|
|
206
|
+
initial_config
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
type: "script-template",
|
|
211
|
+
templateId: GA_PROPERTIES_TEMPLATE,
|
|
212
|
+
params: {
|
|
213
|
+
properties: properties2
|
|
179
214
|
}
|
|
180
215
|
}
|
|
181
216
|
]
|
|
182
217
|
};
|
|
183
218
|
}
|
|
184
|
-
|
|
219
|
+
const eventParams = buildEventParams(event);
|
|
220
|
+
const properties = {};
|
|
221
|
+
if (userId) {
|
|
222
|
+
properties.user_id = userId;
|
|
223
|
+
}
|
|
224
|
+
if (userProperties) {
|
|
225
|
+
properties.user_properties = userProperties;
|
|
226
|
+
}
|
|
227
|
+
if (event.origin === "client") {
|
|
228
|
+
if (preferClientSideForClientEvents || !apiSecret) {
|
|
229
|
+
return {
|
|
230
|
+
items: [
|
|
231
|
+
{
|
|
232
|
+
type: "script-template",
|
|
233
|
+
templateId: GA_EVENT_TEMPLATE,
|
|
234
|
+
params: {
|
|
235
|
+
eventId: event.eventId,
|
|
236
|
+
eventName: toGA4EventName(event.type),
|
|
237
|
+
eventParams,
|
|
238
|
+
properties
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
]
|
|
242
|
+
};
|
|
243
|
+
}
|
|
185
244
|
await sendToMeasurementProtocol({
|
|
186
245
|
measurementId,
|
|
187
246
|
apiSecret,
|
|
@@ -189,12 +248,29 @@ function googleAnalyticsBackend(opts) {
|
|
|
189
248
|
userId,
|
|
190
249
|
userProperties,
|
|
191
250
|
eventName: toGA4EventName(event.type),
|
|
192
|
-
eventParams
|
|
251
|
+
eventParams,
|
|
193
252
|
userAgent: getUserAgent(event),
|
|
194
253
|
clientIp: getClientIp(event),
|
|
195
254
|
debugMode
|
|
196
255
|
});
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
if (!apiSecret) {
|
|
259
|
+
return void 0;
|
|
197
260
|
}
|
|
261
|
+
await sendToMeasurementProtocol({
|
|
262
|
+
measurementId,
|
|
263
|
+
apiSecret,
|
|
264
|
+
clientId,
|
|
265
|
+
userId,
|
|
266
|
+
userProperties,
|
|
267
|
+
eventName: toGA4EventName(event.type),
|
|
268
|
+
eventParams,
|
|
269
|
+
userAgent: getUserAgent(event),
|
|
270
|
+
clientIp: getClientIp(event),
|
|
271
|
+
debugMode
|
|
272
|
+
});
|
|
273
|
+
return void 0;
|
|
198
274
|
},
|
|
199
275
|
updateEvent() {
|
|
200
276
|
}
|
package/dist/backends/gtm.js
CHANGED
|
@@ -22,6 +22,7 @@ __export(gtm_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(gtm_exports);
|
|
24
24
|
const GTM_INIT_TEMPLATE_ID = "gtm-init";
|
|
25
|
+
const GTM_INIT_DATA_TEMPLATE_ID = "gtm-init-data";
|
|
25
26
|
const GTM_PAGEVIEW_TEMPLATE_ID = "gtm-pageview";
|
|
26
27
|
const GTM_EVENT_TEMPLATE_ID = "gtm-event";
|
|
27
28
|
function toSnakeCase(str) {
|
|
@@ -37,21 +38,27 @@ function googleTagManagerBackend(opts) {
|
|
|
37
38
|
return {
|
|
38
39
|
[GTM_INIT_TEMPLATE_ID]: {
|
|
39
40
|
items: [
|
|
41
|
+
// GTM script loader - run once
|
|
40
42
|
{
|
|
41
43
|
body: [
|
|
42
44
|
"window.dataLayer = window.dataLayer || [];",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
" 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);",
|
|
49
|
-
" })(window,document,'script','dataLayer','{{containerId}}');",
|
|
50
|
-
"}"
|
|
45
|
+
"(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':",
|
|
46
|
+
"new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],",
|
|
47
|
+
"j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=",
|
|
48
|
+
"'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);",
|
|
49
|
+
"})(window,document,'script','dataLayer','{{containerId}}');"
|
|
51
50
|
].join("\n")
|
|
52
51
|
}
|
|
53
52
|
]
|
|
54
53
|
},
|
|
54
|
+
[GTM_INIT_DATA_TEMPLATE_ID]: {
|
|
55
|
+
items: [
|
|
56
|
+
// Initial data push - run when params change (e.g., user logs in)
|
|
57
|
+
{
|
|
58
|
+
body: "dataLayer.push({{json(initialData)}});"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
55
62
|
[GTM_PAGEVIEW_TEMPLATE_ID]: {
|
|
56
63
|
items: [
|
|
57
64
|
{
|
|
@@ -122,6 +129,11 @@ function googleTagManagerBackend(opts) {
|
|
|
122
129
|
if (event.clientContext) {
|
|
123
130
|
return {
|
|
124
131
|
items: [
|
|
132
|
+
{
|
|
133
|
+
type: "script-template",
|
|
134
|
+
templateId: GTM_INIT_DATA_TEMPLATE_ID,
|
|
135
|
+
params: { initialData }
|
|
136
|
+
},
|
|
125
137
|
{
|
|
126
138
|
type: "script-template",
|
|
127
139
|
templateId: GTM_PAGEVIEW_TEMPLATE_ID,
|
|
@@ -137,6 +149,11 @@ function googleTagManagerBackend(opts) {
|
|
|
137
149
|
templateId: GTM_INIT_TEMPLATE_ID,
|
|
138
150
|
params: { containerId, initialData }
|
|
139
151
|
},
|
|
152
|
+
{
|
|
153
|
+
type: "script-template",
|
|
154
|
+
templateId: GTM_INIT_DATA_TEMPLATE_ID,
|
|
155
|
+
params: { initialData }
|
|
156
|
+
},
|
|
140
157
|
{
|
|
141
158
|
type: "script-template",
|
|
142
159
|
templateId: GTM_PAGEVIEW_TEMPLATE_ID,
|
|
@@ -3,49 +3,41 @@ import 'next/dist/server/web/spec-extension/cookies';
|
|
|
3
3
|
import 'next/server';
|
|
4
4
|
|
|
5
5
|
declare const tableColumns: readonly [{
|
|
6
|
-
readonly name: "event_id";
|
|
7
|
-
readonly pgType: "TEXT PRIMARY KEY";
|
|
8
|
-
readonly chType: "String";
|
|
9
|
-
}, {
|
|
10
|
-
readonly name: "parent_event_id";
|
|
11
|
-
readonly pgType: "TEXT";
|
|
12
|
-
readonly chType: "Nullable(String)";
|
|
13
|
-
}, {
|
|
14
6
|
readonly name: "timestamp";
|
|
15
|
-
readonly pgType: "TIMESTAMPTZ";
|
|
7
|
+
readonly pgType: "TIMESTAMPTZ NOT NULL";
|
|
16
8
|
readonly chType: "DateTime64(3)";
|
|
17
9
|
}, {
|
|
18
10
|
readonly name: "type";
|
|
19
|
-
readonly pgType: "TEXT";
|
|
11
|
+
readonly pgType: "TEXT NOT NULL";
|
|
20
12
|
readonly chType: "LowCardinality(String)";
|
|
21
13
|
}, {
|
|
22
|
-
readonly name: "
|
|
14
|
+
readonly name: "host";
|
|
23
15
|
readonly pgType: "TEXT";
|
|
24
|
-
readonly chType: "
|
|
16
|
+
readonly chType: "LowCardinality(String)";
|
|
25
17
|
}, {
|
|
26
|
-
readonly name: "
|
|
18
|
+
readonly name: "path";
|
|
27
19
|
readonly pgType: "TEXT";
|
|
28
|
-
readonly chType: "
|
|
20
|
+
readonly chType: "String";
|
|
29
21
|
}, {
|
|
30
|
-
readonly name: "
|
|
22
|
+
readonly name: "method";
|
|
31
23
|
readonly pgType: "TEXT";
|
|
32
|
-
readonly chType: "
|
|
24
|
+
readonly chType: "LowCardinality(String)";
|
|
33
25
|
}, {
|
|
34
|
-
readonly name: "
|
|
26
|
+
readonly name: "user_id";
|
|
35
27
|
readonly pgType: "TEXT";
|
|
36
28
|
readonly chType: "Nullable(String)";
|
|
37
29
|
}, {
|
|
38
|
-
readonly name: "
|
|
30
|
+
readonly name: "anonymous_user_id";
|
|
39
31
|
readonly pgType: "TEXT";
|
|
40
|
-
readonly chType: "
|
|
32
|
+
readonly chType: "Nullable(String)";
|
|
41
33
|
}, {
|
|
42
|
-
readonly name: "
|
|
34
|
+
readonly name: "user_email";
|
|
43
35
|
readonly pgType: "TEXT";
|
|
44
|
-
readonly chType: "
|
|
36
|
+
readonly chType: "Nullable(String)";
|
|
45
37
|
}, {
|
|
46
|
-
readonly name: "
|
|
38
|
+
readonly name: "user_name";
|
|
47
39
|
readonly pgType: "TEXT";
|
|
48
|
-
readonly chType: "String";
|
|
40
|
+
readonly chType: "Nullable(String)";
|
|
49
41
|
}, {
|
|
50
42
|
readonly name: "ip";
|
|
51
43
|
readonly pgType: "INET";
|
|
@@ -62,6 +54,14 @@ declare const tableColumns: readonly [{
|
|
|
62
54
|
readonly name: "locale";
|
|
63
55
|
readonly pgType: "TEXT";
|
|
64
56
|
readonly chType: "LowCardinality(Nullable(String))";
|
|
57
|
+
}, {
|
|
58
|
+
readonly name: "event_id";
|
|
59
|
+
readonly pgType: "TEXT PRIMARY KEY";
|
|
60
|
+
readonly chType: "String";
|
|
61
|
+
}, {
|
|
62
|
+
readonly name: "parent_event_id";
|
|
63
|
+
readonly pgType: "TEXT";
|
|
64
|
+
readonly chType: "Nullable(String)";
|
|
65
65
|
}, {
|
|
66
66
|
readonly name: "server_context";
|
|
67
67
|
readonly pgType: "JSONB";
|
|
@@ -97,21 +97,21 @@ declare function generateChCreateTableSQL(database: string, tableName: string):
|
|
|
97
97
|
declare function isChTableNotFoundError(text: string): boolean;
|
|
98
98
|
/** Row type returned from analytics table queries */
|
|
99
99
|
interface AnalyticsEventRow {
|
|
100
|
-
event_id: string;
|
|
101
|
-
parent_event_id: string | null;
|
|
102
100
|
timestamp: string;
|
|
103
101
|
type: string;
|
|
104
|
-
|
|
102
|
+
host: string;
|
|
103
|
+
path: string;
|
|
104
|
+
method: string;
|
|
105
105
|
user_id: string | null;
|
|
106
|
+
anonymous_user_id: string | null;
|
|
106
107
|
user_email: string | null;
|
|
107
108
|
user_name: string | null;
|
|
108
|
-
host: string;
|
|
109
|
-
method: string;
|
|
110
|
-
path: string;
|
|
111
109
|
ip: string | null;
|
|
112
110
|
referer: string | null;
|
|
113
111
|
user_agent: string | null;
|
|
114
112
|
locale: string | null;
|
|
113
|
+
event_id: string;
|
|
114
|
+
parent_event_id: string | null;
|
|
115
115
|
server_context: Record<string, unknown>;
|
|
116
116
|
client_context: Record<string, unknown>;
|
|
117
117
|
user_traits: Record<string, unknown>;
|
package/dist/backends/lib/db.js
CHANGED
|
@@ -29,21 +29,21 @@ __export(db_exports, {
|
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(db_exports);
|
|
31
31
|
const tableColumns = [
|
|
32
|
-
{ name: "
|
|
33
|
-
{ name: "
|
|
34
|
-
{ name: "
|
|
35
|
-
{ name: "
|
|
36
|
-
{ name: "
|
|
32
|
+
{ name: "timestamp", pgType: "TIMESTAMPTZ NOT NULL", chType: "DateTime64(3)" },
|
|
33
|
+
{ name: "type", pgType: "TEXT NOT NULL", chType: "LowCardinality(String)" },
|
|
34
|
+
{ name: "host", pgType: "TEXT", chType: "LowCardinality(String)" },
|
|
35
|
+
{ name: "path", pgType: "TEXT", chType: "String" },
|
|
36
|
+
{ name: "method", pgType: "TEXT", chType: "LowCardinality(String)" },
|
|
37
37
|
{ name: "user_id", pgType: "TEXT", chType: "Nullable(String)" },
|
|
38
|
+
{ name: "anonymous_user_id", pgType: "TEXT", chType: "Nullable(String)" },
|
|
38
39
|
{ name: "user_email", pgType: "TEXT", chType: "Nullable(String)" },
|
|
39
40
|
{ name: "user_name", pgType: "TEXT", chType: "Nullable(String)" },
|
|
40
|
-
{ name: "host", pgType: "TEXT", chType: "LowCardinality(String)" },
|
|
41
|
-
{ name: "method", pgType: "TEXT", chType: "LowCardinality(String)" },
|
|
42
|
-
{ name: "path", pgType: "TEXT", chType: "String" },
|
|
43
41
|
{ name: "ip", pgType: "INET", chType: "Nullable(IPv6)" },
|
|
44
42
|
{ name: "referer", pgType: "TEXT", chType: "Nullable(String)" },
|
|
45
43
|
{ name: "user_agent", pgType: "TEXT", chType: "Nullable(String)" },
|
|
46
44
|
{ name: "locale", pgType: "TEXT", chType: "LowCardinality(Nullable(String))" },
|
|
45
|
+
{ name: "event_id", pgType: "TEXT PRIMARY KEY", chType: "String" },
|
|
46
|
+
{ name: "parent_event_id", pgType: "TEXT", chType: "Nullable(String)" },
|
|
47
47
|
{ name: "server_context", pgType: "JSONB", chType: "JSON" },
|
|
48
48
|
{ name: "client_context", pgType: "JSONB", chType: "JSON" },
|
|
49
49
|
{ name: "user_traits", pgType: "JSONB", chType: "JSON" },
|
|
@@ -77,21 +77,21 @@ function extractCommonFields(event) {
|
|
|
77
77
|
return Object.keys(rest).length > 0 ? rest : null;
|
|
78
78
|
})() : null;
|
|
79
79
|
return {
|
|
80
|
-
event_id: event.eventId,
|
|
81
|
-
parent_event_id: event.parentEventId ?? null,
|
|
82
80
|
timestamp: event.collectedAt,
|
|
83
81
|
type: event.type,
|
|
84
|
-
|
|
82
|
+
host,
|
|
83
|
+
path,
|
|
84
|
+
method,
|
|
85
85
|
user_id: event.userContext?.userId ?? null,
|
|
86
|
+
anonymous_user_id: event.anonymousUserId ?? null,
|
|
86
87
|
user_email: event.userContext?.traits?.email ?? null,
|
|
87
88
|
user_name: event.userContext?.traits?.name ?? null,
|
|
88
|
-
host,
|
|
89
|
-
method,
|
|
90
|
-
path,
|
|
91
89
|
ip: ip || null,
|
|
92
90
|
referer: clientCtx.referer ?? null,
|
|
93
91
|
user_agent: clientCtx.user_agent ?? null,
|
|
94
92
|
locale: clientCtx.locale ?? null,
|
|
93
|
+
event_id: event.eventId,
|
|
94
|
+
parent_event_id: event.parentEventId ?? null,
|
|
95
95
|
serverContextRest,
|
|
96
96
|
clientContextRest: clientCtx.rest,
|
|
97
97
|
userTraitsRest,
|
|
@@ -118,8 +118,8 @@ function eventToJsonRow(event) {
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
function generatePgCreateTableSQL(tableName) {
|
|
121
|
-
const pk = tableColumns
|
|
122
|
-
const alters = tableColumns.
|
|
121
|
+
const pk = tableColumns.find((c) => c.pgType.includes("PRIMARY KEY"));
|
|
122
|
+
const alters = tableColumns.filter((c) => c !== pk).map((col) => `ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS ${col.name} ${col.pgType};`).join("\n");
|
|
123
123
|
return `CREATE TABLE IF NOT EXISTS ${tableName} (${pk.name} ${pk.pgType});
|
|
124
124
|
${alters}`;
|
|
125
125
|
}
|
|
@@ -129,7 +129,7 @@ function isPgTableNotFoundError(err) {
|
|
|
129
129
|
function generateChCreateTableSQL(database, tableName) {
|
|
130
130
|
const fullTable = `${database}.${tableName}`;
|
|
131
131
|
const createCols = tableColumns.filter((c) => c.name === "event_id" || c.name === "timestamp").map((c) => `${c.name} ${c.chType}`).join(", ");
|
|
132
|
-
const create = `CREATE TABLE IF NOT EXISTS ${fullTable} (${createCols}) ENGINE = ReplacingMergeTree() PARTITION BY toYYYYMM(timestamp) ORDER BY event_id;`;
|
|
132
|
+
const create = `CREATE TABLE IF NOT EXISTS ${fullTable} (${createCols}) ENGINE = ReplacingMergeTree() PARTITION BY toYYYYMM(timestamp) ORDER BY (timestamp, event_id);`;
|
|
133
133
|
const alters = tableColumns.filter((c) => c.name !== "event_id" && c.name !== "timestamp").map((c) => `ALTER TABLE ${fullTable} ADD COLUMN IF NOT EXISTS ${c.name} ${c.chType};`).join("\n");
|
|
134
134
|
return `${create}
|
|
135
135
|
${alters}`;
|