@objectstack/service-analytics 4.0.2 → 4.0.4
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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +15 -0
- package/README.md +395 -0
- package/package.json +5 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/service-analytics@4.0.
|
|
2
|
+
> @objectstack/service-analytics@4.0.4 build /home/runner/work/framework/framework/packages/services/service-analytics
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[32m18.81 KB[39m
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[32m43.38 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in 110ms
|
|
16
13
|
[32mCJS[39m [1mdist/index.cjs [22m[32m20.02 KB[39m
|
|
17
14
|
[32mCJS[39m [1mdist/index.cjs.map [22m[32m44.22 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 115ms
|
|
16
|
+
[32mESM[39m [1mdist/index.js [22m[32m18.81 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m43.38 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 118ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 15849ms
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m9.26 KB[39m
|
|
22
22
|
[32mDTS[39m [1mdist/index.d.cts [22m[32m9.26 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog — @objectstack/service-analytics
|
|
2
2
|
|
|
3
|
+
## 4.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [326b66b]
|
|
8
|
+
- @objectstack/spec@4.0.4
|
|
9
|
+
- @objectstack/core@4.0.4
|
|
10
|
+
|
|
11
|
+
## 4.0.3
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- @objectstack/spec@4.0.3
|
|
16
|
+
- @objectstack/core@4.0.3
|
|
17
|
+
|
|
3
18
|
## 4.0.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
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/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-analytics",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Analytics Service for ObjectStack — implements IAnalyticsService with multi-driver strategy pattern (NativeSQL, ObjectQL, InMemory)",
|
|
6
6
|
"type": "module",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "4.0.
|
|
18
|
-
"@objectstack/spec": "4.0.
|
|
17
|
+
"@objectstack/core": "4.0.4",
|
|
18
|
+
"@objectstack/spec": "4.0.4"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^25.
|
|
21
|
+
"@types/node": "^25.6.0",
|
|
22
22
|
"typescript": "^6.0.2",
|
|
23
|
-
"vitest": "^4.1.
|
|
23
|
+
"vitest": "^4.1.4"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup --config ../../../tsup.config.ts",
|