@umituz/web-traffic 1.0.6

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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/package.json +61 -0
  4. package/src/domains/affiliate/aggregates/affiliate.aggregate.ts +98 -0
  5. package/src/domains/affiliate/entities/affiliate-visit.entity.ts +52 -0
  6. package/src/domains/affiliate/index.ts +22 -0
  7. package/src/domains/affiliate/repositories/affiliate.repository.interface.ts +26 -0
  8. package/src/domains/affiliate/value-objects/affiliate-id.vo.ts +35 -0
  9. package/src/domains/affiliate/value-objects/site-id.vo.ts +33 -0
  10. package/src/domains/analytics/entities/analytics.entity.ts +33 -0
  11. package/src/domains/analytics/index.ts +18 -0
  12. package/src/domains/analytics/repositories/analytics.repository.interface.ts +19 -0
  13. package/src/domains/conversion/aggregates/order.aggregate.ts +85 -0
  14. package/src/domains/conversion/entities/order-item.entity.ts +27 -0
  15. package/src/domains/conversion/events/conversion-recorded.domain-event.ts +30 -0
  16. package/src/domains/conversion/index.ts +21 -0
  17. package/src/domains/conversion/repositories/conversion.repository.interface.ts +13 -0
  18. package/src/domains/conversion/value-objects/money.vo.ts +55 -0
  19. package/src/domains/tracking/aggregates/session.aggregate.ts +154 -0
  20. package/src/domains/tracking/application/tracking-command.service.ts +109 -0
  21. package/src/domains/tracking/entities/event.entity.ts +48 -0
  22. package/src/domains/tracking/entities/pageview.entity.ts +52 -0
  23. package/src/domains/tracking/events/event-tracked.domain-event.ts +28 -0
  24. package/src/domains/tracking/events/pageview-tracked.domain-event.ts +31 -0
  25. package/src/domains/tracking/index.ts +37 -0
  26. package/src/domains/tracking/repositories/event.repository.interface.ts +29 -0
  27. package/src/domains/tracking/value-objects/device-info.vo.ts +163 -0
  28. package/src/domains/tracking/value-objects/event-id.vo.ts +36 -0
  29. package/src/domains/tracking/value-objects/session-id.vo.ts +36 -0
  30. package/src/domains/tracking/value-objects/utm-parameters.vo.ts +75 -0
  31. package/src/index.ts +16 -0
  32. package/src/infrastructure/analytics/http-analytics.repository.impl.ts +60 -0
  33. package/src/infrastructure/index.ts +19 -0
  34. package/src/infrastructure/repositories/http-event.repository.impl.ts +160 -0
  35. package/src/infrastructure/tracking/web-traffic.service.ts +188 -0
  36. package/src/presentation/context.tsx +43 -0
  37. package/src/presentation/hooks.ts +78 -0
  38. package/src/presentation/index.ts +11 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Analytics Repository Interface
3
+ * @description Repository interface for Analytics queries (Domain Layer)
4
+ */
5
+
6
+ import type { AnalyticsData } from '../entities/analytics.entity';
7
+
8
+ export interface AnalyticsQuery {
9
+ readonly startDate: Date;
10
+ readonly endDate: Date;
11
+ readonly path?: string;
12
+ }
13
+
14
+ export interface IAnalyticsRepository {
15
+ getAnalytics(query: AnalyticsQuery): Promise<AnalyticsData | null>;
16
+ getPageviews(query: AnalyticsQuery): Promise<number>;
17
+ getSessions(query: AnalyticsQuery): Promise<number>;
18
+ getVisitors(query: AnalyticsQuery): Promise<number>;
19
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Order Aggregate Root
3
+ * @description Manages conversion order and its items
4
+ */
5
+
6
+ import type { OrderItem } from '../entities/order-item.entity';
7
+ import type { EventId } from '../../tracking/value-objects/event-id.vo';
8
+ import { Money } from '../value-objects/money.vo';
9
+
10
+ export interface OrderCreateInput {
11
+ sessionId: string;
12
+ orderId: string;
13
+ items: Array<{
14
+ id: string;
15
+ name: string;
16
+ price: number;
17
+ quantity: number;
18
+ }>;
19
+ currency?: string;
20
+ }
21
+
22
+ export class Order {
23
+ readonly id: EventId;
24
+ readonly sessionId: string;
25
+ readonly orderId: string;
26
+ private items: OrderItem[];
27
+ private total: Money;
28
+ readonly createdAt: number;
29
+
30
+ constructor(input: OrderCreateInput & { id: EventId }) {
31
+ this.id = input.id;
32
+ this.sessionId = input.sessionId;
33
+ this.orderId = input.orderId;
34
+ this.createdAt = Date.now();
35
+ this.items = [];
36
+
37
+ // Calculate total and create items
38
+ let totalAmount = 0;
39
+ for (const item of input.items) {
40
+ const orderItem: OrderItem = {
41
+ id: item.id,
42
+ name: item.name,
43
+ price: item.price,
44
+ quantity: item.quantity,
45
+ };
46
+ this.items.push(orderItem);
47
+ totalAmount += item.price * item.quantity;
48
+ }
49
+
50
+ this.total = new Money(totalAmount, input.currency);
51
+
52
+ Object.freeze(this.items);
53
+ Object.freeze(this.id);
54
+ Object.freeze(this.sessionId);
55
+ Object.freeze(this.orderId);
56
+ Object.freeze(this.createdAt);
57
+ }
58
+
59
+ getTotal(): Money {
60
+ return this.total;
61
+ }
62
+
63
+ getItems(): OrderItem[] {
64
+ return [...this.items];
65
+ }
66
+
67
+ getItemCount(): number {
68
+ return this.items.reduce((sum, item) => sum + item.quantity, 0);
69
+ }
70
+
71
+ isValid(): boolean {
72
+ return this.items.length > 0 && this.total.getAmount() > 0;
73
+ }
74
+
75
+ toJSON() {
76
+ return {
77
+ id: this.id.toString(),
78
+ sessionId: this.sessionId,
79
+ orderId: this.orderId,
80
+ items: this.getItems(),
81
+ total: this.total.toJSON(),
82
+ createdAt: this.createdAt,
83
+ };
84
+ }
85
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OrderItem Entity
3
+ * @description Represents an item in a conversion order
4
+ */
5
+
6
+ export interface OrderItem {
7
+ readonly id: string;
8
+ readonly name: string;
9
+ readonly price: number;
10
+ readonly quantity: number;
11
+ }
12
+
13
+ export interface OrderItemCreateInput {
14
+ id: string;
15
+ name: string;
16
+ price: number;
17
+ quantity: number;
18
+ }
19
+
20
+ export function createOrderItem(input: OrderItemCreateInput): OrderItem {
21
+ return {
22
+ id: input.id,
23
+ name: input.name,
24
+ price: input.price,
25
+ quantity: input.quantity,
26
+ };
27
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Conversion Recorded Domain Event
3
+ * @description Published when a conversion is recorded
4
+ */
5
+
6
+ import type { Money } from '../value-objects/money.vo';
7
+
8
+ export class ConversionRecorded {
9
+ readonly eventType = 'ConversionRecorded';
10
+ readonly orderId: string;
11
+ readonly sessionId: string;
12
+ readonly revenue: Money;
13
+ readonly itemCount: number;
14
+ readonly occurredAt: number;
15
+
16
+ constructor(params: {
17
+ orderId: string;
18
+ sessionId: string;
19
+ revenue: Money;
20
+ itemCount: number;
21
+ occurredAt?: number;
22
+ }) {
23
+ this.orderId = params.orderId;
24
+ this.sessionId = params.sessionId;
25
+ this.revenue = params.revenue;
26
+ this.itemCount = params.itemCount;
27
+ this.occurredAt = params.occurredAt ?? Date.now();
28
+ Object.freeze(this);
29
+ }
30
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Conversion Domain Export
3
+ * Subpath: @umituz/web-traffic/conversion
4
+ */
5
+
6
+ // Aggregates
7
+ export { Order } from './aggregates/order.aggregate';
8
+ export type { OrderCreateInput } from './aggregates/order.aggregate';
9
+
10
+ // Entities
11
+ export { createOrderItem } from './entities/order-item.entity';
12
+ export type { OrderItem, OrderItemCreateInput } from './entities/order-item.entity';
13
+
14
+ // Value Objects
15
+ export { Money } from './value-objects/money.vo';
16
+
17
+ // Repository Interfaces
18
+ export type { IConversionRepository } from './repositories/conversion.repository.interface';
19
+
20
+ // Domain Events
21
+ export { ConversionRecorded } from './events/conversion-recorded.domain-event';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Conversion Repository Interface
3
+ * @description Repository interface for Order persistence (Domain Layer)
4
+ */
5
+
6
+ import type { Order } from '../aggregates/order.aggregate';
7
+
8
+ export interface IConversionRepository {
9
+ save(order: Order): Promise<void>;
10
+ findById(orderId: string): Promise<Order | null>;
11
+ findBySessionId(sessionId: string): Promise<Order[]>;
12
+ delete(orderId: string): Promise<void>;
13
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Money Value Object
3
+ * @description Immutable value object for monetary values
4
+ */
5
+
6
+ export class Money {
7
+ private readonly amount: number;
8
+ private readonly currency: string;
9
+
10
+ constructor(amount: number, currency: string = 'USD') {
11
+ if (amount < 0) {
12
+ throw new Error('Amount cannot be negative');
13
+ }
14
+ if (!currency || currency.length !== 3) {
15
+ throw new Error('Currency must be a valid ISO 4217 code');
16
+ }
17
+ this.amount = Math.round(amount * 100) / 100; // Round to 2 decimal places
18
+ this.currency = currency.toUpperCase();
19
+ Object.freeze(this);
20
+ }
21
+
22
+ getAmount(): number {
23
+ return this.amount;
24
+ }
25
+
26
+ getCurrency(): string {
27
+ return this.currency;
28
+ }
29
+
30
+ add(other: Money): Money {
31
+ if (this.currency !== other.currency) {
32
+ throw new Error('Cannot add money with different currencies');
33
+ }
34
+ return new Money(this.amount + other.amount, this.currency);
35
+ }
36
+
37
+ multiply(factor: number): Money {
38
+ return new Money(this.amount * factor, this.currency);
39
+ }
40
+
41
+ equals(other: Money): boolean {
42
+ return this.amount === other.amount && this.currency === other.currency;
43
+ }
44
+
45
+ toJSON() {
46
+ return {
47
+ amount: this.amount,
48
+ currency: this.currency,
49
+ };
50
+ }
51
+
52
+ static zero(currency: string = 'USD'): Money {
53
+ return new Money(0, currency);
54
+ }
55
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Session Aggregate Root
3
+ * @description Manages user session and its events within consistency boundary
4
+ */
5
+
6
+ import type { Pageview } from '../entities/pageview.entity';
7
+ import type { Event } from '../entities/event.entity';
8
+ import { SessionId } from '../value-objects/session-id.vo';
9
+ import type { SiteId } from '../../affiliate/value-objects/site-id.vo';
10
+ import type { DeviceInfo } from '../value-objects/device-info.vo';
11
+
12
+ export interface SessionCreateInput {
13
+ deviceId: string;
14
+ siteId: SiteId;
15
+ deviceInfo: DeviceInfo;
16
+ startTime?: number;
17
+ }
18
+
19
+ export class Session {
20
+ readonly id: SessionId;
21
+ readonly deviceId: string;
22
+ readonly siteId: SiteId;
23
+ readonly deviceInfo: DeviceInfo;
24
+ readonly startTime: number;
25
+ private endTime: number | null;
26
+ private events: Event[];
27
+ private pageviews: Pageview[];
28
+ private eventCount: number;
29
+ private pageviewCount: number;
30
+ private entryPage: string | null;
31
+ private exitPage: string | null;
32
+
33
+ constructor(input: SessionCreateInput & { id: SessionId }) {
34
+ this.id = input.id;
35
+ this.deviceId = input.deviceId;
36
+ this.siteId = input.siteId;
37
+ this.deviceInfo = input.deviceInfo;
38
+ this.startTime = input.startTime ?? Date.now();
39
+ this.endTime = null;
40
+ this.events = [];
41
+ this.pageviews = [];
42
+ this.eventCount = 0;
43
+ this.pageviewCount = 0;
44
+ this.entryPage = null;
45
+ this.exitPage = null;
46
+ Object.freeze(this.id);
47
+ Object.freeze(this.deviceId);
48
+ Object.freeze(this.siteId);
49
+ Object.freeze(this.deviceInfo);
50
+ Object.freeze(this.startTime);
51
+ }
52
+
53
+ // Aggregate root methods - maintain consistency
54
+ addEvent(event: Event): void {
55
+ if (this.isExpired()) {
56
+ throw new Error('Cannot add event to expired session');
57
+ }
58
+ this.events.push(event);
59
+ this.eventCount++;
60
+ this.updateLastActivity();
61
+ }
62
+
63
+ addPageview(pageview: Pageview): void {
64
+ if (this.isExpired()) {
65
+ throw new Error('Cannot add pageview to expired session');
66
+ }
67
+ this.pageviews.push(pageview);
68
+ this.pageviewCount++;
69
+ this.exitPage = pageview.path;
70
+ if (!this.entryPage) {
71
+ this.entryPage = pageview.path;
72
+ }
73
+ this.updateLastActivity();
74
+ }
75
+
76
+ getEntryPage(): string | null {
77
+ return this.entryPage;
78
+ }
79
+
80
+ getExitPage(): string | null {
81
+ return this.exitPage;
82
+ }
83
+
84
+ getSiteId(): SiteId {
85
+ return this.siteId;
86
+ }
87
+
88
+ getVisitorId(): string {
89
+ return this.deviceId;
90
+ }
91
+
92
+ getDeviceInfo(): DeviceInfo {
93
+ return this.deviceInfo;
94
+ }
95
+
96
+ close(): void {
97
+ if (this.endTime) {
98
+ throw new Error('Session already closed');
99
+ }
100
+ this.endTime = Date.now();
101
+ }
102
+
103
+ isExpired(timeoutMs: number = 30 * 60 * 1000): boolean {
104
+ if (this.endTime) {
105
+ return true;
106
+ }
107
+ return Date.now() - this.startTime > timeoutMs;
108
+ }
109
+
110
+ isActive(timeoutMs: number = 30 * 60 * 1000): boolean {
111
+ return !this.isExpired(timeoutMs);
112
+ }
113
+
114
+ getDuration(): number {
115
+ const end = this.endTime ?? Date.now();
116
+ return end - this.startTime;
117
+ }
118
+
119
+ getEventCount(): number {
120
+ return this.eventCount;
121
+ }
122
+
123
+ getPageviewCount(): number {
124
+ return this.pageviewCount;
125
+ }
126
+
127
+ getEvents(): Event[] {
128
+ return [...this.events];
129
+ }
130
+
131
+ getPageviews(): Pageview[] {
132
+ return [...this.pageviews];
133
+ }
134
+
135
+ private updateLastActivity(): void {
136
+ // Could emit domain event here
137
+ }
138
+
139
+ toJSON() {
140
+ return {
141
+ id: this.id.toString(),
142
+ deviceId: this.deviceId,
143
+ siteId: this.siteId.toString(),
144
+ deviceInfo: this.deviceInfo.toJSON(),
145
+ startTime: this.startTime,
146
+ endTime: this.endTime,
147
+ eventCount: this.eventCount,
148
+ pageviewCount: this.pageviewCount,
149
+ entryPage: this.entryPage,
150
+ exitPage: this.exitPage,
151
+ duration: this.getDuration(),
152
+ };
153
+ }
154
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Tracking Command Service
3
+ * @description Application service for tracking commands (use-cases)
4
+ */
5
+
6
+ import { EventId } from '../../tracking/value-objects/event-id.vo';
7
+ import { SessionId } from '../../tracking/value-objects/session-id.vo';
8
+ import { UTMParameters } from '../../tracking/value-objects/utm-parameters.vo';
9
+ import { Session } from '../../tracking/aggregates/session.aggregate';
10
+ import { Event } from '../../tracking/entities/event.entity';
11
+ import { Pageview } from '../../tracking/entities/pageview.entity';
12
+ import type { ISessionRepository, IEventRepository, IPageviewRepository } from '../../tracking/repositories/event.repository.interface';
13
+ import type { EventTracked } from '../events/event-tracked.domain-event';
14
+ import type { PageviewTracked } from '../events/pageview-tracked.domain-event';
15
+
16
+ export interface TrackingCommandResult {
17
+ success: boolean;
18
+ eventId?: string;
19
+ error?: string;
20
+ }
21
+
22
+ export class TrackingCommandService {
23
+ constructor(
24
+ private readonly sessionRepo: ISessionRepository,
25
+ private readonly eventRepo: IEventRepository,
26
+ private readonly pageviewRepo: IPageviewRepository
27
+ ) {}
28
+
29
+ async trackEvent(
30
+ sessionId: string,
31
+ eventName: string,
32
+ properties: Record<string, unknown> = {}
33
+ ): Promise<TrackingCommandResult> {
34
+ try {
35
+ const sessionIdVo = new SessionId(sessionId);
36
+ const eventId = EventId.generate();
37
+
38
+ const event = new Event({
39
+ id: eventId,
40
+ sessionId: sessionIdVo,
41
+ name: eventName,
42
+ properties,
43
+ });
44
+
45
+ // Get or create session
46
+ let session = await this.sessionRepo.findById(sessionIdVo);
47
+ if (!session) {
48
+ throw new Error('Session not found');
49
+ }
50
+
51
+ session.addEvent(event);
52
+ await this.eventRepo.save(event);
53
+ await this.sessionRepo.save(session);
54
+
55
+ return {
56
+ success: true,
57
+ eventId: eventId.toString(),
58
+ };
59
+ } catch (error) {
60
+ return {
61
+ success: false,
62
+ error: error instanceof Error ? error.message : 'Unknown error',
63
+ };
64
+ }
65
+ }
66
+
67
+ async trackPageview(
68
+ sessionId: string,
69
+ path: string,
70
+ referrer: string | null = null,
71
+ utmParams?: { source?: string; medium?: string; campaign?: string; term?: string; content?: string }
72
+ ): Promise<TrackingCommandResult> {
73
+ try {
74
+ const sessionIdVo = new SessionId(sessionId);
75
+ const pageviewId = EventId.generate();
76
+
77
+ const utmParameters = utmParams ? new UTMParameters(utmParams) : null;
78
+
79
+ const pageview = new Pageview({
80
+ id: pageviewId,
81
+ sessionId: sessionIdVo,
82
+ siteId: new SessionId(data.sessionId).getSiteId(),
83
+ path,
84
+ referrer,
85
+ utmParameters,
86
+ });
87
+
88
+ // Get or create session
89
+ let session = await this.sessionRepo.findById(sessionIdVo);
90
+ if (!session) {
91
+ throw new Error('Session not found');
92
+ }
93
+
94
+ session.addPageview(pageview);
95
+ await this.pageviewRepo.save(pageview);
96
+ await this.sessionRepo.save(session);
97
+
98
+ return {
99
+ success: true,
100
+ eventId: pageviewId.toString(),
101
+ };
102
+ } catch (error) {
103
+ return {
104
+ success: false,
105
+ error: error instanceof Error ? error.message : 'Unknown error',
106
+ };
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Event Entity
3
+ * @description Represents a tracked analytics event using value objects
4
+ */
5
+
6
+ import { EventId } from '../value-objects/event-id.vo';
7
+ import type { SessionId } from '../value-objects/session-id.vo';
8
+
9
+ export interface EventCreateInput {
10
+ sessionId: SessionId;
11
+ name: string;
12
+ properties: Record<string, unknown>;
13
+ }
14
+
15
+ export class Event {
16
+ readonly id: EventId;
17
+ readonly sessionId: SessionId;
18
+ readonly name: string;
19
+ readonly properties: Record<string, unknown>;
20
+ readonly timestamp: number;
21
+
22
+ constructor(input: EventCreateInput & { id: EventId; timestamp?: number }) {
23
+ this.id = input.id;
24
+ this.sessionId = input.sessionId;
25
+ this.name = input.name;
26
+ this.properties = { ...input.properties };
27
+ this.timestamp = input.timestamp ?? Date.now();
28
+ Object.freeze(this.properties);
29
+ }
30
+
31
+ hasProperty(key: string): boolean {
32
+ return key in this.properties;
33
+ }
34
+
35
+ getProperty<T = unknown>(key: string): T | undefined {
36
+ return this.properties[key] as T;
37
+ }
38
+
39
+ toJSON() {
40
+ return {
41
+ id: this.id.toString(),
42
+ sessionId: this.sessionId.toString(),
43
+ name: this.name,
44
+ properties: this.properties,
45
+ timestamp: this.timestamp,
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pageview Entity
3
+ * @description Represents a page view using value objects
4
+ */
5
+
6
+ import { EventId } from '../value-objects/event-id.vo';
7
+ import type { SessionId } from '../value-objects/session-id.vo';
8
+ import type { SiteId } from '../../affiliate/value-objects/site-id.vo';
9
+ import { UTMParameters } from '../value-objects/utm-parameters.vo';
10
+
11
+ export interface PageviewCreateInput {
12
+ sessionId: SessionId;
13
+ siteId: SiteId;
14
+ path: string;
15
+ referrer: string | null;
16
+ utmParameters: UTMParameters | null;
17
+ }
18
+
19
+ export class Pageview {
20
+ readonly id: EventId;
21
+ readonly sessionId: SessionId;
22
+ readonly siteId: SiteId;
23
+ readonly path: string;
24
+ readonly referrer: string | null;
25
+ readonly utmParameters: UTMParameters | null;
26
+ readonly timestamp: number;
27
+
28
+ constructor(input: PageviewCreateInput & { id: EventId; timestamp?: number }) {
29
+ this.id = input.id;
30
+ this.sessionId = input.sessionId;
31
+ this.siteId = input.siteId;
32
+ this.path = input.path;
33
+ this.referrer = input.referrer;
34
+ this.utmParameters = input.utmParameters;
35
+ this.timestamp = input.timestamp ?? Date.now();
36
+ }
37
+
38
+ hasUTMParameters(): boolean {
39
+ return this.utmParameters?.hasAnyUTM() ?? false;
40
+ }
41
+
42
+ toJSON() {
43
+ return {
44
+ id: this.id.toString(),
45
+ sessionId: this.sessionId.toString(),
46
+ path: this.path,
47
+ referrer: this.referrer,
48
+ utmParameters: this.utmParameters?.toJSON() ?? null,
49
+ timestamp: this.timestamp,
50
+ };
51
+ }
52
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Event Tracked Domain Event
3
+ * @description Published when an analytics event is tracked
4
+ */
5
+
6
+ import type { EventId } from '../../tracking/value-objects/event-id.vo';
7
+ import type { SessionId } from '../../tracking/value-objects/session-id.vo';
8
+
9
+ export class EventTracked {
10
+ readonly eventType = 'EventTracked';
11
+ readonly eventId: EventId;
12
+ readonly sessionId: SessionId;
13
+ readonly eventName: string;
14
+ readonly occurredAt: number;
15
+
16
+ constructor(params: {
17
+ eventId: EventId;
18
+ sessionId: SessionId;
19
+ eventName: string;
20
+ occurredAt?: number;
21
+ }) {
22
+ this.eventId = params.eventId;
23
+ this.sessionId = params.sessionId;
24
+ this.eventName = params.eventName;
25
+ this.occurredAt = params.occurredAt ?? Date.now();
26
+ Object.freeze(this);
27
+ }
28
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pageview Tracked Domain Event
3
+ * @description Published when a pageview is tracked
4
+ */
5
+
6
+ import type { EventId } from '../../tracking/value-objects/event-id.vo';
7
+ import type { SessionId } from '../../tracking/value-objects/session-id.vo';
8
+
9
+ export class PageviewTracked {
10
+ readonly eventType = 'PageviewTracked';
11
+ readonly pageviewId: EventId;
12
+ readonly sessionId: SessionId;
13
+ readonly path: string;
14
+ readonly hasUTM: boolean;
15
+ readonly occurredAt: number;
16
+
17
+ constructor(params: {
18
+ pageviewId: EventId;
19
+ sessionId: SessionId;
20
+ path: string;
21
+ hasUTM: boolean;
22
+ occurredAt?: number;
23
+ }) {
24
+ this.pageviewId = params.pageviewId;
25
+ this.sessionId = params.sessionId;
26
+ this.path = params.path;
27
+ this.hasUTM = params.hasUTM;
28
+ this.occurredAt = params.occurredAt ?? Date.now();
29
+ Object.freeze(this);
30
+ }
31
+ }