@objectstack/service-analytics 4.0.3 → 4.0.5

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,395 @@
1
+ # @objectstack/service-analytics
2
+
3
+ Analytics Service for ObjectStack — implements `IAnalyticsService` with multi-driver strategy pattern (NativeSQL, ObjectQL, InMemory).
4
+
5
+ ## Features
6
+
7
+ - **Multi-Driver Architecture**: Choose the right execution strategy for your analytics queries
8
+ - **NativeSQL**: Direct SQL execution for maximum performance on large datasets
9
+ - **ObjectQL**: Leverage ObjectStack's query engine for metadata-aware analytics
10
+ - **InMemory**: Fast aggregations on small datasets without database round-trips
11
+ - **Aggregation Functions**: SUM, COUNT, AVG, MIN, MAX, GROUP BY, HAVING
12
+ - **Time Series Analysis**: Time-based aggregations and grouping
13
+ - **Custom Metrics**: Define and track custom business metrics
14
+ - **Dashboard Integration**: Auto-generated REST endpoints for visualization
15
+ - **Type-Safe**: Full TypeScript support with inferred result types
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add @objectstack/service-analytics
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ ```typescript
26
+ import { defineStack } from '@objectstack/spec';
27
+ import { ServiceAnalytics } from '@objectstack/service-analytics';
28
+
29
+ const stack = defineStack({
30
+ services: [
31
+ ServiceAnalytics.configure({
32
+ defaultDriver: 'objectql', // or 'sql', 'memory'
33
+ enableCaching: true,
34
+ }),
35
+ ],
36
+ });
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ ```typescript
42
+ interface AnalyticsServiceConfig {
43
+ /** Default execution driver */
44
+ defaultDriver?: 'sql' | 'objectql' | 'memory';
45
+
46
+ /** Enable query result caching */
47
+ enableCaching?: boolean;
48
+
49
+ /** Cache TTL in seconds (default: 300) */
50
+ cacheTTL?: number;
51
+
52
+ /** Maximum result set size for in-memory driver */
53
+ maxMemoryResults?: number;
54
+ }
55
+ ```
56
+
57
+ ## Service API
58
+
59
+ ```typescript
60
+ // Get analytics service from kernel
61
+ const analytics = kernel.getService<IAnalyticsService>('analytics');
62
+ ```
63
+
64
+ ### Basic Aggregations
65
+
66
+ ```typescript
67
+ // Count records
68
+ const totalOrders = await analytics.count({
69
+ object: 'order',
70
+ filters: [{ field: 'status', operator: 'eq', value: 'completed' }],
71
+ });
72
+
73
+ // Sum field values
74
+ const totalRevenue = await analytics.sum({
75
+ object: 'order',
76
+ field: 'amount',
77
+ filters: [{ field: 'created_at', operator: 'gte', value: '2024-01-01' }],
78
+ });
79
+
80
+ // Calculate average
81
+ const avgOrderValue = await analytics.avg({
82
+ object: 'order',
83
+ field: 'amount',
84
+ });
85
+
86
+ // Find min/max
87
+ const highestOrder = await analytics.max({
88
+ object: 'order',
89
+ field: 'amount',
90
+ });
91
+ ```
92
+
93
+ ### Group By Aggregations
94
+
95
+ ```typescript
96
+ // Revenue by product category
97
+ const revenueByCategory = await analytics.groupBy({
98
+ object: 'order_item',
99
+ groupBy: ['product.category'],
100
+ aggregations: [
101
+ { function: 'sum', field: 'total', as: 'revenue' },
102
+ { function: 'count', as: 'order_count' },
103
+ ],
104
+ });
105
+
106
+ // Result format:
107
+ // [
108
+ // { category: 'Electronics', revenue: 125000, order_count: 342 },
109
+ // { category: 'Clothing', revenue: 98000, order_count: 567 },
110
+ // ]
111
+ ```
112
+
113
+ ### Time Series Analytics
114
+
115
+ ```typescript
116
+ // Daily revenue for the past 30 days
117
+ const dailyRevenue = await analytics.timeSeries({
118
+ object: 'order',
119
+ dateField: 'created_at',
120
+ interval: 'day',
121
+ aggregations: [
122
+ { function: 'sum', field: 'amount', as: 'revenue' },
123
+ { function: 'count', as: 'orders' },
124
+ ],
125
+ filters: [
126
+ {
127
+ field: 'created_at',
128
+ operator: 'gte',
129
+ value: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
130
+ },
131
+ ],
132
+ });
133
+
134
+ // Result format:
135
+ // [
136
+ // { date: '2024-01-01', revenue: 12500, orders: 45 },
137
+ // { date: '2024-01-02', revenue: 15200, orders: 52 },
138
+ // ]
139
+ ```
140
+
141
+ ### Custom Metrics
142
+
143
+ ```typescript
144
+ // Define a metric
145
+ analytics.defineMetric({
146
+ name: 'monthly_recurring_revenue',
147
+ description: 'MRR from active subscriptions',
148
+ calculation: {
149
+ object: 'subscription',
150
+ aggregation: 'sum',
151
+ field: 'amount',
152
+ filters: [{ field: 'status', operator: 'eq', value: 'active' }],
153
+ },
154
+ });
155
+
156
+ // Query the metric
157
+ const mrr = await analytics.getMetric('monthly_recurring_revenue');
158
+ ```
159
+
160
+ ## Multi-Driver Strategy
161
+
162
+ ### When to Use Each Driver
163
+
164
+ #### NativeSQL Driver
165
+ **Best for**: Large datasets, complex joins, database-specific optimizations
166
+
167
+ ```typescript
168
+ const result = await analytics.query({
169
+ driver: 'sql',
170
+ object: 'order',
171
+ aggregations: [{ function: 'sum', field: 'amount' }],
172
+ groupBy: ['customer_id'],
173
+ having: [{ field: 'sum_amount', operator: 'gt', value: 10000 }],
174
+ });
175
+ ```
176
+
177
+ **Advantages:**
178
+ - Direct SQL execution for maximum performance
179
+ - Leverages database indexes and query optimization
180
+ - Handles millions of records efficiently
181
+
182
+ **Limitations:**
183
+ - Bypasses ObjectStack metadata layer
184
+ - May miss field-level transformations
185
+ - Less portable across databases
186
+
187
+ #### ObjectQL Driver
188
+ **Best for**: Metadata-aware analytics, cross-object aggregations
189
+
190
+ ```typescript
191
+ const result = await analytics.query({
192
+ driver: 'objectql',
193
+ object: 'opportunity',
194
+ aggregations: [
195
+ { function: 'sum', field: 'amount' },
196
+ { function: 'count' },
197
+ ],
198
+ groupBy: ['account.industry'],
199
+ });
200
+ ```
201
+
202
+ **Advantages:**
203
+ - Respects object/field metadata and permissions
204
+ - Handles formula fields and computed values
205
+ - Consistent with ObjectQL query behavior
206
+
207
+ **Limitations:**
208
+ - Slightly slower than direct SQL
209
+ - Additional abstraction layer
210
+
211
+ #### InMemory Driver
212
+ **Best for**: Small datasets, pre-filtered results, real-time dashboards
213
+
214
+ ```typescript
215
+ const result = await analytics.query({
216
+ driver: 'memory',
217
+ object: 'task',
218
+ aggregations: [{ function: 'count' }],
219
+ groupBy: ['status'],
220
+ });
221
+ ```
222
+
223
+ **Advantages:**
224
+ - Zero database round-trips for cached data
225
+ - Instant results for small datasets
226
+ - Useful for client-side analytics
227
+
228
+ **Limitations:**
229
+ - Limited to `maxMemoryResults` (default: 10,000)
230
+ - Requires data to be loaded into memory first
231
+
232
+ ## REST API Endpoints
233
+
234
+ When used with `@objectstack/rest`:
235
+
236
+ ```
237
+ POST /api/v1/analytics/count # Count records
238
+ POST /api/v1/analytics/sum # Sum field values
239
+ POST /api/v1/analytics/avg # Calculate average
240
+ POST /api/v1/analytics/min # Find minimum
241
+ POST /api/v1/analytics/max # Find maximum
242
+ POST /api/v1/analytics/group-by # Group by aggregation
243
+ POST /api/v1/analytics/time-series # Time series analysis
244
+ GET /api/v1/analytics/metrics # List custom metrics
245
+ GET /api/v1/analytics/metrics/:name # Get metric value
246
+ ```
247
+
248
+ ## Dashboard Integration
249
+
250
+ ```typescript
251
+ // Define a dashboard with multiple metrics
252
+ const salesDashboard = {
253
+ title: 'Sales Dashboard',
254
+ metrics: [
255
+ {
256
+ title: 'Total Revenue',
257
+ query: {
258
+ object: 'order',
259
+ aggregation: 'sum',
260
+ field: 'amount',
261
+ },
262
+ },
263
+ {
264
+ title: 'Revenue by Region',
265
+ query: {
266
+ object: 'order',
267
+ aggregations: [{ function: 'sum', field: 'amount', as: 'revenue' }],
268
+ groupBy: ['account.billing_region'],
269
+ },
270
+ },
271
+ ],
272
+ };
273
+
274
+ // Execute all dashboard queries
275
+ const dashboardData = await analytics.executeDashboard(salesDashboard);
276
+ ```
277
+
278
+ ## Advanced Features
279
+
280
+ ### Query Caching
281
+
282
+ ```typescript
283
+ // Enable caching for expensive queries
284
+ const result = await analytics.query({
285
+ object: 'order',
286
+ aggregations: [{ function: 'sum', field: 'amount' }],
287
+ cache: {
288
+ enabled: true,
289
+ ttl: 600, // 10 minutes
290
+ },
291
+ });
292
+
293
+ // Invalidate cache when data changes
294
+ analytics.invalidateCache('order');
295
+ ```
296
+
297
+ ### Comparative Analytics
298
+
299
+ ```typescript
300
+ // Compare current vs. previous period
301
+ const comparison = await analytics.compare({
302
+ object: 'order',
303
+ aggregation: 'sum',
304
+ field: 'amount',
305
+ currentPeriod: {
306
+ start: '2024-01-01',
307
+ end: '2024-01-31',
308
+ },
309
+ comparisonPeriod: {
310
+ start: '2023-12-01',
311
+ end: '2023-12-31',
312
+ },
313
+ });
314
+
315
+ // Result:
316
+ // {
317
+ // current: 125000,
318
+ // comparison: 110000,
319
+ // change: 15000,
320
+ // percentChange: 13.64
321
+ // }
322
+ ```
323
+
324
+ ### Funnel Analysis
325
+
326
+ ```typescript
327
+ // Define a conversion funnel
328
+ const funnel = await analytics.funnel({
329
+ steps: [
330
+ { object: 'lead', stage: 'new' },
331
+ { object: 'lead', stage: 'qualified' },
332
+ { object: 'opportunity', stage: 'proposal' },
333
+ { object: 'opportunity', stage: 'closed_won' },
334
+ ],
335
+ dateRange: {
336
+ start: '2024-01-01',
337
+ end: '2024-01-31',
338
+ },
339
+ });
340
+
341
+ // Result:
342
+ // {
343
+ // steps: [
344
+ // { stage: 'new', count: 1000, percentage: 100 },
345
+ // { stage: 'qualified', count: 450, percentage: 45 },
346
+ // { stage: 'proposal', count: 200, percentage: 20 },
347
+ // { stage: 'closed_won', count: 75, percentage: 7.5 },
348
+ // ],
349
+ // overallConversion: 0.075
350
+ // }
351
+ ```
352
+
353
+ ## Contract Implementation
354
+
355
+ Implements `IAnalyticsService` from `@objectstack/spec/contracts`:
356
+
357
+ ```typescript
358
+ interface IAnalyticsService {
359
+ count(options: CountOptions): Promise<number>;
360
+ sum(options: AggregationOptions): Promise<number>;
361
+ avg(options: AggregationOptions): Promise<number>;
362
+ min(options: AggregationOptions): Promise<number>;
363
+ max(options: AggregationOptions): Promise<number>;
364
+ groupBy(options: GroupByOptions): Promise<AggregationResult[]>;
365
+ timeSeries(options: TimeSeriesOptions): Promise<TimeSeriesResult[]>;
366
+ defineMetric(metric: MetricDefinition): void;
367
+ getMetric(name: string): Promise<number | AggregationResult[]>;
368
+ }
369
+ ```
370
+
371
+ ## Performance Optimization
372
+
373
+ 1. **Choose the Right Driver**: Use SQL for large datasets, InMemory for small
374
+ 2. **Enable Caching**: Cache expensive queries with appropriate TTL
375
+ 3. **Optimize Filters**: Filter early to reduce dataset size
376
+ 4. **Use Indexes**: Ensure database indexes on frequently queried fields
377
+ 5. **Batch Queries**: Execute multiple metrics in a single dashboard query
378
+
379
+ ## Best Practices
380
+
381
+ 1. **Driver Selection**: Start with ObjectQL, optimize to SQL if needed
382
+ 2. **Metric Definitions**: Define reusable metrics for consistency
383
+ 3. **Cache Strategy**: Cache expensive queries, invalidate on data changes
384
+ 4. **Time Series**: Use appropriate intervals (hour/day/week/month)
385
+ 5. **Group By**: Limit grouping dimensions to avoid explosion of result sets
386
+
387
+ ## License
388
+
389
+ Apache-2.0
390
+
391
+ ## See Also
392
+
393
+ - [@objectstack/objectql](../../objectql/)
394
+ - [@objectstack/spec/contracts](../../spec/src/contracts/)
395
+ - [Analytics Guide](/content/docs/guides/analytics/)