@astralibx/email-analytics 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @astralibx/email-analytics
2
+
3
+ Event-level email tracking with automatic daily aggregation and pre-built query APIs. Record individual send/deliver/bounce/open/click events, aggregate them into daily stats by account, rule, or template, and query time-series data through REST endpoints or the programmatic API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @astralibx/email-analytics
9
+ ```
10
+
11
+ **Peer dependencies** (install separately):
12
+
13
+ ```bash
14
+ npm install mongoose express
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import mongoose from 'mongoose';
21
+ import express from 'express';
22
+ import { createEmailAnalytics } from '@astralibx/email-analytics';
23
+
24
+ const conn = mongoose.createConnection('mongodb://localhost:27017/myapp');
25
+
26
+ const analytics = createEmailAnalytics({
27
+ db: { connection: conn, collectionPrefix: 'myapp_' },
28
+ options: {
29
+ eventTTLDays: 90,
30
+ timezone: 'Asia/Kolkata',
31
+ },
32
+ });
33
+
34
+ // Mount REST routes
35
+ const app = express();
36
+ app.use('/api/analytics', analytics.routes);
37
+
38
+ // Record an event programmatically
39
+ await analytics.events.record({
40
+ type: 'sent',
41
+ accountId: '507f1f77bcf86cd799439011',
42
+ recipientEmail: 'user@example.com',
43
+ ruleId: '507f1f77bcf86cd799439012',
44
+ });
45
+
46
+ // Run daily aggregation
47
+ await analytics.aggregator.aggregateDaily();
48
+
49
+ // Query overview stats
50
+ const overview = await analytics.query.getOverview(
51
+ new Date('2025-01-01'),
52
+ new Date('2025-01-31'),
53
+ );
54
+ ```
55
+
56
+ ## Features
57
+
58
+ - **Event Recording** -- single and batch insert with automatic timestamps ([docs](docs/event-recording.md))
59
+ - **Daily Aggregation** -- rolls up raw events into per-day stats by account, rule, template, and overall ([docs](docs/aggregation.md))
60
+ - **Range Aggregation** -- backfill or re-aggregate any date range ([docs](docs/aggregation.md))
61
+ - **Time-Series Queries** -- overview, timeline (daily/weekly/monthly), per-account, per-rule, per-template ([docs](docs/querying.md))
62
+ - **REST API** -- six endpoints with date-range query params ([docs](docs/api-routes.md))
63
+ - **TTL Cleanup** -- MongoDB TTL index auto-expires old events; manual purge also available ([docs](docs/event-recording.md#ttl-and-cleanup))
64
+ - **Programmatic API** -- use services directly without HTTP ([docs](docs/programmatic-api.md))
65
+ - **Error Handling** -- typed error classes for validation, date range, and aggregation failures ([docs](docs/error-handling.md))
66
+
67
+ ## Integration with Other Packages
68
+
69
+ Wire hooks from `@astralibx/email-account-manager` and `@astralibx/email-rule-engine` to automatically record analytics events. See the [integration guide](docs/integration.md) for full examples.
70
+
71
+ ```typescript
72
+ import { createEmailAccountManager } from '@astralibx/email-account-manager';
73
+ import { createEmailRuleEngine } from '@astralibx/email-rule-engine';
74
+ import { createEmailAnalytics } from '@astralibx/email-analytics';
75
+
76
+ const analytics = createEmailAnalytics({ db: { connection: conn } });
77
+ const accountManager = createEmailAccountManager({ db: { connection: conn } });
78
+ const ruleEngine = createEmailRuleEngine({ db: { connection: conn } });
79
+
80
+ // Record events from rule engine execution
81
+ ruleEngine.on('emailSent', async (detail) => {
82
+ await analytics.events.record({
83
+ type: 'sent',
84
+ accountId: detail.accountId,
85
+ ruleId: detail.ruleId,
86
+ templateId: detail.templateId,
87
+ recipientEmail: detail.recipientEmail,
88
+ });
89
+ });
90
+
91
+ ruleEngine.on('emailFailed', async (detail) => {
92
+ await analytics.events.record({
93
+ type: 'failed',
94
+ accountId: detail.accountId,
95
+ ruleId: detail.ruleId,
96
+ recipientEmail: detail.recipientEmail,
97
+ metadata: { error: detail.error },
98
+ });
99
+ });
100
+ ```
101
+
102
+ ## Documentation
103
+
104
+ | Guide | Description |
105
+ |-------|-------------|
106
+ | [Configuration](docs/configuration.md) | Full config reference |
107
+ | [Event Recording](docs/event-recording.md) | Recording events, batch insert, TTL |
108
+ | [Aggregation](docs/aggregation.md) | Daily aggregation, range backfill |
109
+ | [Querying](docs/querying.md) | Overview, timeline, grouped stats |
110
+ | [API Routes](docs/api-routes.md) | REST endpoints reference |
111
+ | [Programmatic API](docs/programmatic-api.md) | Using services directly |
112
+ | [Integration](docs/integration.md) | Wiring with account-manager and rule-engine |
113
+ | [Error Handling](docs/error-handling.md) | Error classes and codes |
114
+
115
+ ## Exported Types
116
+
117
+ ```typescript
118
+ // Core types
119
+ export type { EmailAnalyticsConfig } from './types/config.types';
120
+ export type { EmailEvent, CreateEventInput } from './types/event.types';
121
+ export type { DailyStats, AccountStats, RuleStats, TemplateStats, OverviewStats, TimelineEntry } from './types/stats.types';
122
+
123
+ // Constants
124
+ export { EVENT_TYPE, AGGREGATION_INTERVAL, STATS_GROUP_BY } from './constants';
125
+ export type { EventType, AggregationInterval, StatsGroupBy } from './constants';
126
+
127
+ // Errors
128
+ export { AlxAnalyticsError, ConfigValidationError, InvalidDateRangeError, AggregationError } from './errors';
129
+ ```
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,267 @@
1
+ import { Request, Response, Router } from 'express';
2
+ import * as mongoose from 'mongoose';
3
+ import { Connection, Model, Types, HydratedDocument, Schema } from 'mongoose';
4
+ import { LogAdapter, AlxError } from '@astralibx/core';
5
+ export { ConfigValidationError } from '@astralibx/core';
6
+
7
+ declare const EVENT_TYPE: {
8
+ readonly Sent: "sent";
9
+ readonly Delivered: "delivered";
10
+ readonly Bounced: "bounced";
11
+ readonly Complained: "complained";
12
+ readonly Opened: "opened";
13
+ readonly Clicked: "clicked";
14
+ readonly Unsubscribed: "unsubscribed";
15
+ readonly Failed: "failed";
16
+ };
17
+ type EventType = (typeof EVENT_TYPE)[keyof typeof EVENT_TYPE];
18
+ declare const AGGREGATION_INTERVAL: {
19
+ readonly Daily: "daily";
20
+ readonly Weekly: "weekly";
21
+ readonly Monthly: "monthly";
22
+ };
23
+ type AggregationInterval = (typeof AGGREGATION_INTERVAL)[keyof typeof AGGREGATION_INTERVAL];
24
+ declare const STATS_GROUP_BY: {
25
+ readonly Account: "account";
26
+ readonly Rule: "rule";
27
+ readonly Template: "template";
28
+ };
29
+ type StatsGroupBy = (typeof STATS_GROUP_BY)[keyof typeof STATS_GROUP_BY];
30
+
31
+ interface EmailAnalyticsConfig {
32
+ db: {
33
+ connection: Connection;
34
+ collectionPrefix?: string;
35
+ };
36
+ logger?: LogAdapter;
37
+ options?: {
38
+ eventTTLDays?: number;
39
+ timezone?: string;
40
+ aggregationSchedule?: AggregationInterval[];
41
+ };
42
+ }
43
+
44
+ interface IEmailEvent {
45
+ type: EventType;
46
+ accountId: Types.ObjectId;
47
+ ruleId?: Types.ObjectId;
48
+ templateId?: Types.ObjectId;
49
+ recipientEmail: string;
50
+ identifierId?: Types.ObjectId;
51
+ metadata?: Record<string, unknown>;
52
+ timestamp: Date;
53
+ createdAt: Date;
54
+ updatedAt: Date;
55
+ }
56
+ type EmailEventDocument = HydratedDocument<IEmailEvent>;
57
+ interface EmailEventStatics {
58
+ record(event: {
59
+ type: EventType;
60
+ accountId: string;
61
+ ruleId?: string;
62
+ templateId?: string;
63
+ recipientEmail: string;
64
+ identifierId?: string;
65
+ metadata?: Record<string, unknown>;
66
+ timestamp?: Date;
67
+ }): Promise<EmailEventDocument>;
68
+ findByDateRange(start: Date, end: Date, filters?: {
69
+ type?: EventType;
70
+ accountId?: string;
71
+ ruleId?: string;
72
+ templateId?: string;
73
+ }): Promise<EmailEventDocument[]>;
74
+ }
75
+ type EmailEventModel = Model<IEmailEvent> & EmailEventStatics;
76
+ interface CreateEmailEventSchemaOptions {
77
+ collectionName?: string;
78
+ eventTTLDays?: number;
79
+ }
80
+ declare function createEmailEventSchema(options?: CreateEmailEventSchemaOptions): Schema<IEmailEvent, Model<IEmailEvent, any, any, any, mongoose.Document<unknown, any, IEmailEvent, any, {}> & IEmailEvent & {
81
+ _id: Types.ObjectId;
82
+ } & {
83
+ __v: number;
84
+ }, any>, {}, {}, {}, {}, mongoose.DefaultSchemaOptions, IEmailEvent, mongoose.Document<unknown, {}, mongoose.FlatRecord<IEmailEvent>, {}, mongoose.DefaultSchemaOptions> & mongoose.FlatRecord<IEmailEvent> & {
85
+ _id: Types.ObjectId;
86
+ } & {
87
+ __v: number;
88
+ }>;
89
+
90
+ interface IAnalyticsStats {
91
+ date: string;
92
+ interval: AggregationInterval;
93
+ accountId: Types.ObjectId | null;
94
+ ruleId: Types.ObjectId | null;
95
+ templateId: Types.ObjectId | null;
96
+ sent: number;
97
+ delivered: number;
98
+ bounced: number;
99
+ complained: number;
100
+ opened: number;
101
+ clicked: number;
102
+ unsubscribed: number;
103
+ failed: number;
104
+ createdAt: Date;
105
+ updatedAt: Date;
106
+ }
107
+ type AnalyticsStatsDocument = HydratedDocument<IAnalyticsStats>;
108
+ interface AnalyticsStatsStatics {
109
+ upsertStats(date: string, interval: AggregationInterval, dimensions: {
110
+ accountId?: string;
111
+ ruleId?: string;
112
+ templateId?: string;
113
+ }, increments: Partial<Record<'sent' | 'delivered' | 'bounced' | 'complained' | 'opened' | 'clicked' | 'unsubscribed' | 'failed', number>>): Promise<AnalyticsStatsDocument>;
114
+ }
115
+ type AnalyticsStatsModel = Model<IAnalyticsStats> & AnalyticsStatsStatics;
116
+ interface CreateAnalyticsStatsSchemaOptions {
117
+ collectionName?: string;
118
+ }
119
+ declare function createAnalyticsStatsSchema(options?: CreateAnalyticsStatsSchemaOptions): Schema<IAnalyticsStats, Model<IAnalyticsStats, any, any, any, mongoose.Document<unknown, any, IAnalyticsStats, any, {}> & IAnalyticsStats & {
120
+ _id: Types.ObjectId;
121
+ } & {
122
+ __v: number;
123
+ }, any>, {}, {}, {}, {}, mongoose.DefaultSchemaOptions, IAnalyticsStats, mongoose.Document<unknown, {}, mongoose.FlatRecord<IAnalyticsStats>, {}, mongoose.DefaultSchemaOptions> & mongoose.FlatRecord<IAnalyticsStats> & {
124
+ _id: Types.ObjectId;
125
+ } & {
126
+ __v: number;
127
+ }>;
128
+
129
+ type EmailEvent = IEmailEvent;
130
+ interface CreateEventInput {
131
+ type: EventType;
132
+ accountId: string;
133
+ ruleId?: string;
134
+ templateId?: string;
135
+ recipientEmail: string;
136
+ identifierId?: string;
137
+ metadata?: Record<string, unknown>;
138
+ timestamp?: Date;
139
+ }
140
+
141
+ interface EventQueryFilters {
142
+ dateFrom: Date;
143
+ dateTo: Date;
144
+ type?: string;
145
+ accountId?: string;
146
+ ruleId?: string;
147
+ recipientEmail?: string;
148
+ }
149
+ interface PaginatedEvents {
150
+ events: EmailEvent[];
151
+ total: number;
152
+ }
153
+ declare class EventRecorderService {
154
+ private EmailEvent;
155
+ private logger;
156
+ constructor(EmailEvent: EmailEventModel, logger: LogAdapter);
157
+ record(event: CreateEventInput): Promise<void>;
158
+ recordBatch(events: CreateEventInput[]): Promise<void>;
159
+ getEvents(filters: EventQueryFilters, page?: number, limit?: number): Promise<PaginatedEvents>;
160
+ purgeOldEvents(olderThanDays: number): Promise<number>;
161
+ }
162
+
163
+ declare class AggregatorService {
164
+ private EmailEvent;
165
+ private AnalyticsStats;
166
+ private timezone;
167
+ private logger;
168
+ constructor(EmailEvent: EmailEventModel, AnalyticsStats: AnalyticsStatsModel, timezone: string, logger: LogAdapter);
169
+ aggregateDaily(date?: Date): Promise<void>;
170
+ aggregateRange(from: Date, to: Date): Promise<void>;
171
+ private aggregateByAccount;
172
+ private aggregateByRule;
173
+ private aggregateByTemplate;
174
+ private aggregateOverall;
175
+ private getDayStart;
176
+ private getDayEnd;
177
+ private getTimezoneOffsetMs;
178
+ private toDateKey;
179
+ }
180
+
181
+ interface BaseMetrics {
182
+ sent: number;
183
+ delivered: number;
184
+ bounced: number;
185
+ complained: number;
186
+ opened: number;
187
+ clicked: number;
188
+ unsubscribed: number;
189
+ failed: number;
190
+ }
191
+ interface DailyStats extends BaseMetrics {
192
+ date: string;
193
+ }
194
+ interface AccountStats extends BaseMetrics {
195
+ accountId: string;
196
+ }
197
+ interface RuleStats extends BaseMetrics {
198
+ ruleId: string;
199
+ }
200
+ interface TemplateStats extends BaseMetrics {
201
+ templateId: string;
202
+ }
203
+ interface OverviewStats extends BaseMetrics {
204
+ startDate: string;
205
+ endDate: string;
206
+ }
207
+ interface TimelineEntry extends BaseMetrics {
208
+ date: string;
209
+ interval: AggregationInterval;
210
+ }
211
+
212
+ declare class QueryService {
213
+ private AnalyticsStats;
214
+ private logger;
215
+ constructor(AnalyticsStats: AnalyticsStatsModel, logger: LogAdapter);
216
+ getOverview(dateFrom: Date, dateTo: Date): Promise<OverviewStats>;
217
+ getTimeline(dateFrom: Date, dateTo: Date, interval?: AggregationInterval): Promise<TimelineEntry[]>;
218
+ getAccountStats(dateFrom: Date, dateTo: Date): Promise<AccountStats[]>;
219
+ getRuleStats(dateFrom: Date, dateTo: Date): Promise<RuleStats[]>;
220
+ getTemplateStats(dateFrom: Date, dateTo: Date): Promise<TemplateStats[]>;
221
+ private buildTimeGroupId;
222
+ private toDateKey;
223
+ }
224
+
225
+ declare class AlxAnalyticsError extends AlxError {
226
+ readonly code: string;
227
+ constructor(message: string, code: string);
228
+ }
229
+
230
+ declare class InvalidDateRangeError extends AlxAnalyticsError {
231
+ readonly startDate: string;
232
+ readonly endDate: string;
233
+ constructor(startDate: string, endDate: string);
234
+ }
235
+ declare class AggregationError extends AlxAnalyticsError {
236
+ readonly pipeline: string;
237
+ readonly originalError: Error;
238
+ constructor(pipeline: string, originalError: Error);
239
+ }
240
+
241
+ declare function validateConfig(raw: unknown): void;
242
+
243
+ declare function createAnalyticsController(eventRecorder: EventRecorderService, aggregator: AggregatorService, queryService: QueryService): {
244
+ getOverview(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
245
+ getTimeline(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
246
+ getAccountStats(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
247
+ getRuleStats(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
248
+ getTemplateStats(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
249
+ triggerAggregation(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
250
+ };
251
+
252
+ type AnalyticsController = ReturnType<typeof createAnalyticsController>;
253
+ declare function createAnalyticsRoutes(controller: AnalyticsController): Router;
254
+
255
+ interface EmailAnalytics {
256
+ routes: Router;
257
+ events: EventRecorderService;
258
+ aggregator: AggregatorService;
259
+ query: QueryService;
260
+ models: {
261
+ EmailEvent: EmailEventModel;
262
+ AnalyticsStats: AnalyticsStatsModel;
263
+ };
264
+ }
265
+ declare function createEmailAnalytics(config: EmailAnalyticsConfig): EmailAnalytics;
266
+
267
+ export { AGGREGATION_INTERVAL, type AccountStats, AggregationError, type AggregationInterval, AggregatorService, AlxAnalyticsError, type AnalyticsStatsDocument, type AnalyticsStatsModel, type AnalyticsStatsStatics, type BaseMetrics, type CreateAnalyticsStatsSchemaOptions, type CreateEmailEventSchemaOptions, type CreateEventInput, type DailyStats, EVENT_TYPE, type EmailAnalytics, type EmailAnalyticsConfig, type EmailEvent, type EmailEventDocument, type EmailEventModel, type EmailEventStatics, type EventQueryFilters, EventRecorderService, type EventType, type IAnalyticsStats, type IEmailEvent, InvalidDateRangeError, type OverviewStats, type PaginatedEvents, QueryService, type RuleStats, STATS_GROUP_BY, type StatsGroupBy, type TemplateStats, type TimelineEntry, createAnalyticsController, createAnalyticsRoutes, createAnalyticsStatsSchema, createEmailAnalytics, createEmailEventSchema, validateConfig };
@@ -0,0 +1,267 @@
1
+ import { Request, Response, Router } from 'express';
2
+ import * as mongoose from 'mongoose';
3
+ import { Connection, Model, Types, HydratedDocument, Schema } from 'mongoose';
4
+ import { LogAdapter, AlxError } from '@astralibx/core';
5
+ export { ConfigValidationError } from '@astralibx/core';
6
+
7
+ declare const EVENT_TYPE: {
8
+ readonly Sent: "sent";
9
+ readonly Delivered: "delivered";
10
+ readonly Bounced: "bounced";
11
+ readonly Complained: "complained";
12
+ readonly Opened: "opened";
13
+ readonly Clicked: "clicked";
14
+ readonly Unsubscribed: "unsubscribed";
15
+ readonly Failed: "failed";
16
+ };
17
+ type EventType = (typeof EVENT_TYPE)[keyof typeof EVENT_TYPE];
18
+ declare const AGGREGATION_INTERVAL: {
19
+ readonly Daily: "daily";
20
+ readonly Weekly: "weekly";
21
+ readonly Monthly: "monthly";
22
+ };
23
+ type AggregationInterval = (typeof AGGREGATION_INTERVAL)[keyof typeof AGGREGATION_INTERVAL];
24
+ declare const STATS_GROUP_BY: {
25
+ readonly Account: "account";
26
+ readonly Rule: "rule";
27
+ readonly Template: "template";
28
+ };
29
+ type StatsGroupBy = (typeof STATS_GROUP_BY)[keyof typeof STATS_GROUP_BY];
30
+
31
+ interface EmailAnalyticsConfig {
32
+ db: {
33
+ connection: Connection;
34
+ collectionPrefix?: string;
35
+ };
36
+ logger?: LogAdapter;
37
+ options?: {
38
+ eventTTLDays?: number;
39
+ timezone?: string;
40
+ aggregationSchedule?: AggregationInterval[];
41
+ };
42
+ }
43
+
44
+ interface IEmailEvent {
45
+ type: EventType;
46
+ accountId: Types.ObjectId;
47
+ ruleId?: Types.ObjectId;
48
+ templateId?: Types.ObjectId;
49
+ recipientEmail: string;
50
+ identifierId?: Types.ObjectId;
51
+ metadata?: Record<string, unknown>;
52
+ timestamp: Date;
53
+ createdAt: Date;
54
+ updatedAt: Date;
55
+ }
56
+ type EmailEventDocument = HydratedDocument<IEmailEvent>;
57
+ interface EmailEventStatics {
58
+ record(event: {
59
+ type: EventType;
60
+ accountId: string;
61
+ ruleId?: string;
62
+ templateId?: string;
63
+ recipientEmail: string;
64
+ identifierId?: string;
65
+ metadata?: Record<string, unknown>;
66
+ timestamp?: Date;
67
+ }): Promise<EmailEventDocument>;
68
+ findByDateRange(start: Date, end: Date, filters?: {
69
+ type?: EventType;
70
+ accountId?: string;
71
+ ruleId?: string;
72
+ templateId?: string;
73
+ }): Promise<EmailEventDocument[]>;
74
+ }
75
+ type EmailEventModel = Model<IEmailEvent> & EmailEventStatics;
76
+ interface CreateEmailEventSchemaOptions {
77
+ collectionName?: string;
78
+ eventTTLDays?: number;
79
+ }
80
+ declare function createEmailEventSchema(options?: CreateEmailEventSchemaOptions): Schema<IEmailEvent, Model<IEmailEvent, any, any, any, mongoose.Document<unknown, any, IEmailEvent, any, {}> & IEmailEvent & {
81
+ _id: Types.ObjectId;
82
+ } & {
83
+ __v: number;
84
+ }, any>, {}, {}, {}, {}, mongoose.DefaultSchemaOptions, IEmailEvent, mongoose.Document<unknown, {}, mongoose.FlatRecord<IEmailEvent>, {}, mongoose.DefaultSchemaOptions> & mongoose.FlatRecord<IEmailEvent> & {
85
+ _id: Types.ObjectId;
86
+ } & {
87
+ __v: number;
88
+ }>;
89
+
90
+ interface IAnalyticsStats {
91
+ date: string;
92
+ interval: AggregationInterval;
93
+ accountId: Types.ObjectId | null;
94
+ ruleId: Types.ObjectId | null;
95
+ templateId: Types.ObjectId | null;
96
+ sent: number;
97
+ delivered: number;
98
+ bounced: number;
99
+ complained: number;
100
+ opened: number;
101
+ clicked: number;
102
+ unsubscribed: number;
103
+ failed: number;
104
+ createdAt: Date;
105
+ updatedAt: Date;
106
+ }
107
+ type AnalyticsStatsDocument = HydratedDocument<IAnalyticsStats>;
108
+ interface AnalyticsStatsStatics {
109
+ upsertStats(date: string, interval: AggregationInterval, dimensions: {
110
+ accountId?: string;
111
+ ruleId?: string;
112
+ templateId?: string;
113
+ }, increments: Partial<Record<'sent' | 'delivered' | 'bounced' | 'complained' | 'opened' | 'clicked' | 'unsubscribed' | 'failed', number>>): Promise<AnalyticsStatsDocument>;
114
+ }
115
+ type AnalyticsStatsModel = Model<IAnalyticsStats> & AnalyticsStatsStatics;
116
+ interface CreateAnalyticsStatsSchemaOptions {
117
+ collectionName?: string;
118
+ }
119
+ declare function createAnalyticsStatsSchema(options?: CreateAnalyticsStatsSchemaOptions): Schema<IAnalyticsStats, Model<IAnalyticsStats, any, any, any, mongoose.Document<unknown, any, IAnalyticsStats, any, {}> & IAnalyticsStats & {
120
+ _id: Types.ObjectId;
121
+ } & {
122
+ __v: number;
123
+ }, any>, {}, {}, {}, {}, mongoose.DefaultSchemaOptions, IAnalyticsStats, mongoose.Document<unknown, {}, mongoose.FlatRecord<IAnalyticsStats>, {}, mongoose.DefaultSchemaOptions> & mongoose.FlatRecord<IAnalyticsStats> & {
124
+ _id: Types.ObjectId;
125
+ } & {
126
+ __v: number;
127
+ }>;
128
+
129
+ type EmailEvent = IEmailEvent;
130
+ interface CreateEventInput {
131
+ type: EventType;
132
+ accountId: string;
133
+ ruleId?: string;
134
+ templateId?: string;
135
+ recipientEmail: string;
136
+ identifierId?: string;
137
+ metadata?: Record<string, unknown>;
138
+ timestamp?: Date;
139
+ }
140
+
141
+ interface EventQueryFilters {
142
+ dateFrom: Date;
143
+ dateTo: Date;
144
+ type?: string;
145
+ accountId?: string;
146
+ ruleId?: string;
147
+ recipientEmail?: string;
148
+ }
149
+ interface PaginatedEvents {
150
+ events: EmailEvent[];
151
+ total: number;
152
+ }
153
+ declare class EventRecorderService {
154
+ private EmailEvent;
155
+ private logger;
156
+ constructor(EmailEvent: EmailEventModel, logger: LogAdapter);
157
+ record(event: CreateEventInput): Promise<void>;
158
+ recordBatch(events: CreateEventInput[]): Promise<void>;
159
+ getEvents(filters: EventQueryFilters, page?: number, limit?: number): Promise<PaginatedEvents>;
160
+ purgeOldEvents(olderThanDays: number): Promise<number>;
161
+ }
162
+
163
+ declare class AggregatorService {
164
+ private EmailEvent;
165
+ private AnalyticsStats;
166
+ private timezone;
167
+ private logger;
168
+ constructor(EmailEvent: EmailEventModel, AnalyticsStats: AnalyticsStatsModel, timezone: string, logger: LogAdapter);
169
+ aggregateDaily(date?: Date): Promise<void>;
170
+ aggregateRange(from: Date, to: Date): Promise<void>;
171
+ private aggregateByAccount;
172
+ private aggregateByRule;
173
+ private aggregateByTemplate;
174
+ private aggregateOverall;
175
+ private getDayStart;
176
+ private getDayEnd;
177
+ private getTimezoneOffsetMs;
178
+ private toDateKey;
179
+ }
180
+
181
+ interface BaseMetrics {
182
+ sent: number;
183
+ delivered: number;
184
+ bounced: number;
185
+ complained: number;
186
+ opened: number;
187
+ clicked: number;
188
+ unsubscribed: number;
189
+ failed: number;
190
+ }
191
+ interface DailyStats extends BaseMetrics {
192
+ date: string;
193
+ }
194
+ interface AccountStats extends BaseMetrics {
195
+ accountId: string;
196
+ }
197
+ interface RuleStats extends BaseMetrics {
198
+ ruleId: string;
199
+ }
200
+ interface TemplateStats extends BaseMetrics {
201
+ templateId: string;
202
+ }
203
+ interface OverviewStats extends BaseMetrics {
204
+ startDate: string;
205
+ endDate: string;
206
+ }
207
+ interface TimelineEntry extends BaseMetrics {
208
+ date: string;
209
+ interval: AggregationInterval;
210
+ }
211
+
212
+ declare class QueryService {
213
+ private AnalyticsStats;
214
+ private logger;
215
+ constructor(AnalyticsStats: AnalyticsStatsModel, logger: LogAdapter);
216
+ getOverview(dateFrom: Date, dateTo: Date): Promise<OverviewStats>;
217
+ getTimeline(dateFrom: Date, dateTo: Date, interval?: AggregationInterval): Promise<TimelineEntry[]>;
218
+ getAccountStats(dateFrom: Date, dateTo: Date): Promise<AccountStats[]>;
219
+ getRuleStats(dateFrom: Date, dateTo: Date): Promise<RuleStats[]>;
220
+ getTemplateStats(dateFrom: Date, dateTo: Date): Promise<TemplateStats[]>;
221
+ private buildTimeGroupId;
222
+ private toDateKey;
223
+ }
224
+
225
+ declare class AlxAnalyticsError extends AlxError {
226
+ readonly code: string;
227
+ constructor(message: string, code: string);
228
+ }
229
+
230
+ declare class InvalidDateRangeError extends AlxAnalyticsError {
231
+ readonly startDate: string;
232
+ readonly endDate: string;
233
+ constructor(startDate: string, endDate: string);
234
+ }
235
+ declare class AggregationError extends AlxAnalyticsError {
236
+ readonly pipeline: string;
237
+ readonly originalError: Error;
238
+ constructor(pipeline: string, originalError: Error);
239
+ }
240
+
241
+ declare function validateConfig(raw: unknown): void;
242
+
243
+ declare function createAnalyticsController(eventRecorder: EventRecorderService, aggregator: AggregatorService, queryService: QueryService): {
244
+ getOverview(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
245
+ getTimeline(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
246
+ getAccountStats(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
247
+ getRuleStats(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
248
+ getTemplateStats(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
249
+ triggerAggregation(req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
250
+ };
251
+
252
+ type AnalyticsController = ReturnType<typeof createAnalyticsController>;
253
+ declare function createAnalyticsRoutes(controller: AnalyticsController): Router;
254
+
255
+ interface EmailAnalytics {
256
+ routes: Router;
257
+ events: EventRecorderService;
258
+ aggregator: AggregatorService;
259
+ query: QueryService;
260
+ models: {
261
+ EmailEvent: EmailEventModel;
262
+ AnalyticsStats: AnalyticsStatsModel;
263
+ };
264
+ }
265
+ declare function createEmailAnalytics(config: EmailAnalyticsConfig): EmailAnalytics;
266
+
267
+ export { AGGREGATION_INTERVAL, type AccountStats, AggregationError, type AggregationInterval, AggregatorService, AlxAnalyticsError, type AnalyticsStatsDocument, type AnalyticsStatsModel, type AnalyticsStatsStatics, type BaseMetrics, type CreateAnalyticsStatsSchemaOptions, type CreateEmailEventSchemaOptions, type CreateEventInput, type DailyStats, EVENT_TYPE, type EmailAnalytics, type EmailAnalyticsConfig, type EmailEvent, type EmailEventDocument, type EmailEventModel, type EmailEventStatics, type EventQueryFilters, EventRecorderService, type EventType, type IAnalyticsStats, type IEmailEvent, InvalidDateRangeError, type OverviewStats, type PaginatedEvents, QueryService, type RuleStats, STATS_GROUP_BY, type StatsGroupBy, type TemplateStats, type TimelineEntry, createAnalyticsController, createAnalyticsRoutes, createAnalyticsStatsSchema, createEmailAnalytics, createEmailEventSchema, validateConfig };