@wool-so/node 0.3.0 → 0.3.1

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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/index.d.ts +140 -31
  3. package/src/index.js +379 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wool-so/node",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Server-side analytics SDK for Wool.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION: '0.3.0'
1
+ export const VERSION: '0.3.1'
2
2
  export const DEFAULT_ENDPOINT: 'https://wool-api.gurjas1882.workers.dev/v1/events'
3
3
 
4
4
  export type WoolPropertyValue = string | number | boolean | null | undefined
@@ -13,9 +13,9 @@ export interface WoolFetchResponse {
13
13
  export type WoolFetch = (
14
14
  input: string,
15
15
  init: {
16
- method: 'POST'
16
+ method: 'GET' | 'POST'
17
17
  headers: Record<string, string>
18
- body: string
18
+ body?: string
19
19
  }
20
20
  ) => Promise<WoolFetchResponse>
21
21
 
@@ -23,6 +23,14 @@ export interface WoolBaseOptions {
23
23
  endpoint?: string
24
24
  apiUrl?: string
25
25
  apiHost?: string
26
+ apiBaseUrl?: string
27
+ api_base_url?: string
28
+ appUrl?: string
29
+ app_url?: string
30
+ dashboardUrl?: string
31
+ dashboard_url?: string
32
+ baseUrl?: string
33
+ base_url?: string
26
34
  fetch?: WoolFetch
27
35
  enabled?: boolean
28
36
  referrer?: string
@@ -36,16 +44,6 @@ export interface WoolBaseOptions {
36
44
  }
37
45
 
38
46
  export type WoolInitOptions = WoolBaseOptions &
39
- (
40
- | {
41
- projectId: string
42
- project_id?: string
43
- }
44
- | {
45
- projectId?: string
46
- project_id: string
47
- }
48
- ) &
49
47
  (
50
48
  | {
51
49
  apiKey: string
@@ -55,24 +53,13 @@ export type WoolInitOptions = WoolBaseOptions &
55
53
  apiKey?: string
56
54
  api_key: string
57
55
  }
58
- ) &
59
- (
60
- | {
61
- siteUrl: string
62
- site_url?: string
63
- url?: string
64
- }
65
- | {
66
- siteUrl?: string
67
- site_url: string
68
- url?: string
69
- }
70
- | {
71
- siteUrl?: string
72
- site_url?: string
73
- url: string
74
- }
75
- )
56
+ ) & {
57
+ projectId?: string
58
+ project_id?: string
59
+ siteUrl?: string
60
+ site_url?: string
61
+ url?: string
62
+ }
76
63
 
77
64
  export interface WoolIdentifyOptions {
78
65
  groupKey?: string
@@ -109,6 +96,8 @@ export interface WoolRequestContext {
109
96
  }
110
97
 
111
98
  export interface WoolTrackOptions extends WoolRequestContextOptions {
99
+ projectId?: string
100
+ project_id?: string
112
101
  request?: Request | WoolRequestLike
113
102
  req?: Request | WoolRequestLike
114
103
  source?: string
@@ -132,10 +121,91 @@ export interface WoolTrackOptions extends WoolRequestContextOptions {
132
121
  }
133
122
 
134
123
  export interface WoolPageviewOptions extends WoolRequestContextOptions {
124
+ projectId?: string
125
+ project_id?: string
135
126
  request?: Request | WoolRequestLike
136
127
  req?: Request | WoolRequestLike
137
128
  }
138
129
 
130
+ export type WoolDatasetName =
131
+ | 'overview'
132
+ | 'performance'
133
+ | 'revenue'
134
+ | 'events'
135
+ | 'retention'
136
+ | 'goals'
137
+ | 'funnels'
138
+ | 'realtime'
139
+
140
+ export interface WoolDatasetOptions {
141
+ projectId?: string
142
+ project_id?: string
143
+ range?: '24h' | '3d' | '7d' | '14d' | '30d' | '3mo' | '6mo' | '12mo' | string
144
+ currency?: string
145
+ event?: string
146
+ eventName?: string
147
+ selectedEvent?: string
148
+ propertyKey?: string
149
+ propertyValue?: string
150
+ entity?: 'user' | 'group' | string
151
+ granularity?: 'daily' | 'weekly' | 'monthly' | 'day' | 'week' | 'month' | string
152
+ startEvent?: string
153
+ returnEvent?: string
154
+ }
155
+
156
+ export interface WoolProject {
157
+ id: string
158
+ projectId: string
159
+ name: string
160
+ description?: string
161
+ siteUrl?: string
162
+ favicon?: string
163
+ createdAt?: string
164
+ updatedAt?: string
165
+ }
166
+
167
+ export type WoolJsonObject = Record<string, unknown>
168
+
169
+ export interface WoolProjectsResponse {
170
+ projects: WoolProject[]
171
+ }
172
+
173
+ export interface WoolProjectResponse {
174
+ project: WoolProject
175
+ }
176
+
177
+ export type WoolDatasetResponse = WoolProjectResponse & WoolJsonObject
178
+
179
+ export interface WoolDatasetsResponse {
180
+ project?: WoolProject
181
+ overview?: WoolJsonObject
182
+ performance?: WoolJsonObject
183
+ revenue?: WoolJsonObject
184
+ events?: WoolJsonObject
185
+ retention?: WoolJsonObject
186
+ goals?: WoolJsonObject
187
+ funnels?: WoolJsonObject
188
+ realtime?: WoolJsonObject
189
+ }
190
+
191
+ export interface WoolCard {
192
+ key: string
193
+ label: string
194
+ value: number | string
195
+ source: WoolDatasetName
196
+ unit?: string
197
+ currency?: string
198
+ [key: string]: unknown
199
+ }
200
+
201
+ export type WoolCardsByDataset = Record<WoolDatasetName, WoolCard[]>
202
+
203
+ export interface WoolCardsResponse {
204
+ project?: WoolProject
205
+ cards: WoolCardsByDataset
206
+ datasets: WoolDatasetsResponse
207
+ }
208
+
139
209
  export interface WoolResult {
140
210
  ok: boolean
141
211
  status: number
@@ -148,6 +218,19 @@ export interface WoolClient {
148
218
  reset(): void
149
219
  track(name: string, properties?: WoolProperties, options?: WoolTrackOptions): Promise<WoolResult>
150
220
  pageview(options?: WoolPageviewOptions): Promise<WoolResult>
221
+ projects(): Promise<WoolProjectsResponse>
222
+ project(options?: string | WoolDatasetOptions): Promise<WoolProjectResponse>
223
+ dataset(name: WoolDatasetName, options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
224
+ datasets(options?: WoolDatasetOptions): Promise<WoolDatasetsResponse>
225
+ cards(options?: WoolDatasetOptions): Promise<WoolCardsResponse>
226
+ overview(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
227
+ performance(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
228
+ revenue(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
229
+ events(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
230
+ retention(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
231
+ goals(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
232
+ funnels(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
233
+ realtime(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
151
234
  requestContext(input?: Request | WoolRequestLike | string, options?: WoolRequestContextOptions): WoolRequestContext
152
235
  }
153
236
 
@@ -163,6 +246,19 @@ export function identify(retentionId: string, options?: WoolIdentifyOptions): vo
163
246
  export function reset(): void
164
247
  export function track(name: string, properties?: WoolProperties, options?: WoolTrackOptions): Promise<WoolResult>
165
248
  export function pageview(options?: WoolPageviewOptions): Promise<WoolResult>
249
+ export function projects(): Promise<WoolProjectsResponse>
250
+ export function project(options?: string | WoolDatasetOptions): Promise<WoolProjectResponse>
251
+ export function dataset(name: WoolDatasetName, options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
252
+ export function datasets(options?: WoolDatasetOptions): Promise<WoolDatasetsResponse>
253
+ export function cards(options?: WoolDatasetOptions): Promise<WoolCardsResponse>
254
+ export function overview(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
255
+ export function performance(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
256
+ export function revenue(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
257
+ export function events(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
258
+ export function retention(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
259
+ export function goals(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
260
+ export function funnels(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
261
+ export function realtime(options?: WoolDatasetOptions): Promise<WoolDatasetResponse>
166
262
  export function requestContext(
167
263
  input?: Request | WoolRequestLike | string,
168
264
  options?: WoolRequestContextOptions
@@ -185,6 +281,19 @@ export function honoMiddleware(options: WoolInitOptions | WoolClient): HonoMiddl
185
281
  export const wool: WoolClient & {
186
282
  init: typeof init
187
283
  createClient: typeof createClient
284
+ cards: typeof cards
285
+ dataset: typeof dataset
286
+ datasets: typeof datasets
287
+ events: typeof events
288
+ funnels: typeof funnels
289
+ goals: typeof goals
290
+ overview: typeof overview
291
+ performance: typeof performance
292
+ project: typeof project
293
+ projects: typeof projects
294
+ realtime: typeof realtime
295
+ retention: typeof retention
296
+ revenue: typeof revenue
188
297
  requestContext: typeof requestContext
189
298
  expressMiddleware: typeof expressMiddleware
190
299
  fastifyPlugin: typeof fastifyPlugin
package/src/index.js CHANGED
@@ -1,9 +1,23 @@
1
- export const VERSION = '0.3.0'
1
+ export const VERSION = '0.3.1'
2
2
  export const DEFAULT_ENDPOINT = 'https://wool-api.gurjas1882.workers.dev/v1/events'
3
3
 
4
+ const DATASET_NAMES = new Set(['overview', 'performance', 'revenue', 'events', 'retention', 'goals', 'funnels', 'realtime'])
5
+ const DATASET_QUERY_KEYS = {
6
+ overview: ['range'],
7
+ performance: ['range'],
8
+ revenue: ['range', 'currency'],
9
+ events: ['range', 'event', 'propertyKey', 'propertyValue'],
10
+ retention: ['range', 'entity', 'granularity', 'startEvent', 'returnEvent'],
11
+ goals: ['range'],
12
+ funnels: ['range'],
13
+ realtime: []
14
+ }
15
+
4
16
  const IDENTITY_KEY_PATTERN =
5
17
  /^(anonymousId|anonymous_id|retentionId|retention_id|subjectKey|subject_key|groupKey|group_key)$/i
6
18
  const TRACK_OPTION_KEYS = new Set([
19
+ 'projectId',
20
+ 'project_id',
7
21
  'source',
8
22
  'eventId',
9
23
  'id',
@@ -58,10 +72,140 @@ function resolveEndpoint(options) {
58
72
  return cleanText(options.endpoint, 2048) || endpointFromApiUrl(options.apiUrl || options.apiHost) || DEFAULT_ENDPOINT
59
73
  }
60
74
 
75
+ function resolveApiBaseUrl(options) {
76
+ const value = cleanText(
77
+ options.apiBaseUrl ||
78
+ options.api_base_url ||
79
+ options.appUrl ||
80
+ options.app_url ||
81
+ options.dashboardUrl ||
82
+ options.dashboard_url ||
83
+ options.baseUrl ||
84
+ options.base_url,
85
+ 2048
86
+ )
87
+
88
+ return value.replace(/\/+$/, '')
89
+ }
90
+
61
91
  function isPlainObject(value) {
62
92
  return Boolean(value && typeof value === 'object' && !Array.isArray(value))
63
93
  }
64
94
 
95
+ function toNumber(value) {
96
+ const number = Number(value)
97
+ return Number.isFinite(number) ? number : 0
98
+ }
99
+
100
+ function valueAt(source, path) {
101
+ return path.reduce((current, key) => (current && current[key] !== undefined ? current[key] : undefined), source)
102
+ }
103
+
104
+ function card(key, label, value, source, options = {}) {
105
+ return {
106
+ key,
107
+ label,
108
+ value,
109
+ source,
110
+ ...options
111
+ }
112
+ }
113
+
114
+ function topConversion(goals = []) {
115
+ return Math.max(...goals.map((goal) => toNumber(goal?.stats?.conversionRate)), 0)
116
+ }
117
+
118
+ function bestFunnelConversion(funnels = []) {
119
+ return Math.max(...funnels.map((funnel) => toNumber(funnel?.conversionRate)), 0)
120
+ }
121
+
122
+ function lowestFunnelDropoff(funnels = []) {
123
+ return funnels.length ? Math.min(...funnels.map((funnel) => toNumber(funnel?.dropoffRate))) : 0
124
+ }
125
+
126
+ function buildCards(data) {
127
+ const overview = data.overview || {}
128
+ const realtime = data.realtime || {}
129
+ const revenue = data.revenue || {}
130
+ const retention = data.retention || {}
131
+ const goals = data.goals || {}
132
+ const funnels = data.funnels || {}
133
+ const events = data.events || {}
134
+ const performance = data.performance || {}
135
+
136
+ return {
137
+ overview: [
138
+ card('visitors', 'Visitors', toNumber(overview.visitors), 'overview'),
139
+ card('visits', 'Visits', toNumber(overview.visits), 'overview'),
140
+ card('pageviews', 'Pageviews', toNumber(overview.pageviews), 'overview'),
141
+ card('bounceRate', 'Bounce rate', toNumber(overview.bounceRate), 'overview', { unit: 'percent' }),
142
+ card('visitDuration', 'Visit duration', toNumber(overview.visitDuration), 'overview', { unit: 'seconds' })
143
+ ],
144
+ realtime: [
145
+ card('activeVisitors', 'Active visitors', toNumber(realtime.activeVisitors), 'realtime'),
146
+ card('visitors', 'Visitors', toNumber(realtime.visitors), 'realtime'),
147
+ card('pageviews', 'Pageviews', toNumber(realtime.pageviews), 'realtime'),
148
+ card('events', 'Events', toNumber(realtime.events), 'realtime')
149
+ ],
150
+ revenue: [
151
+ card('revenue', 'Revenue', toNumber(valueAt(revenue, ['totals', 'revenue'])), 'revenue', {
152
+ currency: revenue.selectedCurrency || ''
153
+ }),
154
+ card('purchases', 'Purchases', toNumber(valueAt(revenue, ['totals', 'purchases'])), 'revenue'),
155
+ card('purchasers', 'Purchasers', toNumber(valueAt(revenue, ['totals', 'purchasers'])), 'revenue'),
156
+ card('averageOrderValue', 'AOV', toNumber(valueAt(revenue, ['totals', 'averageOrderValue'])), 'revenue', {
157
+ currency: revenue.selectedCurrency || ''
158
+ }),
159
+ card('conversionRate', 'Conversion', toNumber(valueAt(revenue, ['totals', 'conversionRate'])), 'revenue', {
160
+ unit: 'percent'
161
+ })
162
+ ],
163
+ retention: [
164
+ card('cohorts', 'Cohorts', toNumber(valueAt(retention, ['totals', 'cohorts'])), 'retention'),
165
+ card('subjects', 'Subjects', toNumber(valueAt(retention, ['totals', 'subjects'])), 'retention'),
166
+ card('retentionRate', 'Retention', toNumber(valueAt(retention, ['totals', 'retentionRate'])), 'retention', {
167
+ unit: 'percent',
168
+ retained: toNumber(valueAt(retention, ['totals', 'retained']))
169
+ }),
170
+ card('churnRate', 'Churn', toNumber(valueAt(retention, ['totals', 'churnRate'])), 'retention', {
171
+ unit: 'percent',
172
+ churned: toNumber(valueAt(retention, ['totals', 'churned']))
173
+ })
174
+ ],
175
+ goals: [
176
+ card('goals', 'Goals', Array.isArray(goals.goals) ? goals.goals.length : 0, 'goals'),
177
+ card('visitors', 'Visitors', toNumber(valueAt(goals, ['totals', 'visitors'])), 'goals'),
178
+ card('completions', 'Completions', toNumber(valueAt(goals, ['totals', 'completions'])), 'goals'),
179
+ card('topConversion', 'Top conversion', topConversion(goals.goals), 'goals', { unit: 'percent' })
180
+ ],
181
+ funnels: [
182
+ card('funnels', 'Funnels', Array.isArray(funnels.funnels) ? funnels.funnels.length : 0, 'funnels'),
183
+ card('availableGoals', 'Available goals', Array.isArray(funnels.goals) ? funnels.goals.length : 0, 'funnels'),
184
+ card('bestConversion', 'Best conversion', bestFunnelConversion(funnels.funnels), 'funnels', { unit: 'percent' }),
185
+ card('lowestDropoff', 'Lowest dropoff', lowestFunnelDropoff(funnels.funnels), 'funnels', { unit: 'percent' })
186
+ ],
187
+ events: [
188
+ card('events', 'Events', toNumber(valueAt(events, ['totals', 'events'])), 'events'),
189
+ card('visitors', 'Visitors', toNumber(valueAt(events, ['totals', 'visitors'])), 'events'),
190
+ card('visits', 'Visits', toNumber(valueAt(events, ['totals', 'visits'])), 'events'),
191
+ card('totalValue', 'Total value', toNumber(valueAt(events, ['totals', 'totalValue'])), 'events'),
192
+ card('averageDuration', 'Average duration', toNumber(valueAt(events, ['totals', 'averageDuration'])), 'events', {
193
+ unit: 'seconds'
194
+ })
195
+ ],
196
+ performance: (Array.isArray(performance.metrics) ? performance.metrics : []).map((metric) =>
197
+ card(metric.key || metric.name || 'metric', metric.label || metric.key || 'Metric', toNumber(metric.p75), 'performance', {
198
+ unit: metric.unit || 'ms',
199
+ p50: toNumber(metric.p50),
200
+ p75: toNumber(metric.p75),
201
+ p95: toNumber(metric.p95),
202
+ samples: toNumber(metric.samples),
203
+ status: metric.status || ''
204
+ })
205
+ )
206
+ }
207
+ }
208
+
65
209
  function cleanProperties(properties) {
66
210
  if (!isPlainObject(properties)) return {}
67
211
 
@@ -207,13 +351,13 @@ function createState(options) {
207
351
  const apiKey = cleanText(options.apiKey || options.api_key, 4096)
208
352
  const siteUrl = cleanText(options.siteUrl || options.site_url || options.url, 2048)
209
353
 
210
- if (!projectId) throw new WoolError('projectId is required.')
211
354
  if (!apiKey) throw new WoolError('apiKey is required.')
212
355
 
213
356
  return {
214
357
  projectId,
215
358
  apiKey,
216
359
  endpoint: resolveEndpoint(options),
360
+ apiBaseUrl: resolveApiBaseUrl(options),
217
361
  defaultUrl: siteUrl,
218
362
  defaultReferrer: cleanText(options.referrer || options.referer, 2048),
219
363
  enabled: options.enabled !== false,
@@ -229,9 +373,69 @@ function bodyFromResponse(response) {
229
373
  return response?.text ? response.text().catch(() => '') : Promise.resolve('')
230
374
  }
231
375
 
376
+ function parseJson(text) {
377
+ if (!text) return {}
378
+
379
+ try {
380
+ return JSON.parse(text)
381
+ } catch {
382
+ return null
383
+ }
384
+ }
385
+
386
+ function normalizeRetentionGranularity(value) {
387
+ const text = String(value || '').trim().toLowerCase()
388
+ if (text === 'day' || text === 'daily') return 'daily'
389
+ if (text === 'week' || text === 'weekly') return 'weekly'
390
+ if (text === 'month' || text === 'monthly') return 'monthly'
391
+ return value
392
+ }
393
+
232
394
  export function createClient(options = {}) {
233
395
  const state = createState(options)
234
396
 
397
+ function projectIdFrom(options = {}) {
398
+ const projectId = cleanText(options.projectId || options.project_id || state.projectId, 96)
399
+ if (!projectId) throw new WoolError('projectId is required.')
400
+ return projectId
401
+ }
402
+
403
+ function dataApiUrl(path, query = {}) {
404
+ if (!state.apiBaseUrl) {
405
+ throw new WoolError('apiBaseUrl, appUrl, dashboardUrl, or baseUrl is required for analytics reads.')
406
+ }
407
+
408
+ const url = new URL(path, `${state.apiBaseUrl}/`)
409
+ for (const [key, value] of Object.entries(query)) {
410
+ if (value !== undefined && value !== null && String(value).trim()) {
411
+ url.searchParams.set(key, String(value))
412
+ }
413
+ }
414
+
415
+ return url.toString()
416
+ }
417
+
418
+ async function requestJson(path, query = {}) {
419
+ const response = await state.fetch(dataApiUrl(path, query), {
420
+ method: 'GET',
421
+ headers: {
422
+ Accept: 'application/json',
423
+ Authorization: `Bearer ${state.apiKey}`,
424
+ 'X-API-Key': state.apiKey,
425
+ 'X-Wool-SDK': `@wool-so/node/${VERSION}`
426
+ }
427
+ })
428
+ const body = await bodyFromResponse(response)
429
+ const data = parseJson(body)
430
+
431
+ if (!response?.ok) {
432
+ throw new WoolError(data?.error || body || 'Unable to fetch Wool analytics data.', response?.status || 0, response || null)
433
+ }
434
+
435
+ if (data === null) throw new WoolError('Wool analytics response must be valid JSON.', response?.status || 0, response || null)
436
+ return data
437
+ }
438
+
235
439
  async function send(payload) {
236
440
  if (!state.enabled) {
237
441
  return { ok: true, status: 0, skipped: true }
@@ -242,6 +446,7 @@ export function createClient(options = {}) {
242
446
  headers: {
243
447
  Authorization: `Bearer ${state.apiKey}`,
244
448
  'Content-Type': 'application/json',
449
+ 'X-API-Key': state.apiKey,
245
450
  'X-Wool-SDK': `@wool-so/node/${VERSION}`
246
451
  },
247
452
  body: JSON.stringify(payload)
@@ -257,12 +462,14 @@ export function createClient(options = {}) {
257
462
 
258
463
  function basePayload(type, options = {}) {
259
464
  const context = requestContext(options.request || options.req, options)
465
+ const projectId = cleanText(options.projectId || options.project_id || state.projectId, 96)
260
466
  const url = cleanText(context.url || state.defaultUrl, 2048)
261
467
 
468
+ if (!projectId) throw new WoolError('projectId is required.')
262
469
  if (!url) throw new WoolError('url or siteUrl is required.')
263
470
 
264
471
  const payload = {
265
- projectId: state.projectId,
472
+ projectId,
266
473
  type,
267
474
  url,
268
475
  referrer: cleanText(context.referrer || state.defaultReferrer, 2048),
@@ -304,6 +511,97 @@ export function createClient(options = {}) {
304
511
  return send(basePayload('pageview', options))
305
512
  }
306
513
 
514
+ async function projects() {
515
+ return requestJson('/api/projects')
516
+ }
517
+
518
+ async function project(options = {}) {
519
+ const projectId = typeof options === 'string' ? cleanText(options, 96) : projectIdFrom(options)
520
+ if (!projectId) throw new WoolError('projectId is required.')
521
+ return requestJson(`/api/projects/${encodeURIComponent(projectId)}`)
522
+ }
523
+
524
+ function datasetQuery(name, options = {}) {
525
+ const normalized = String(name || '').toLowerCase()
526
+ const keys = DATASET_QUERY_KEYS[normalized] || []
527
+ const query = {}
528
+
529
+ for (const key of keys) {
530
+ if (key === 'event') {
531
+ query.event = options.event || options.eventName || options.selectedEvent
532
+ } else if (key === 'granularity') {
533
+ query.granularity = normalizeRetentionGranularity(options.granularity)
534
+ } else {
535
+ query[key] = options[key]
536
+ }
537
+ }
538
+
539
+ return query
540
+ }
541
+
542
+ async function dataset(name, options = {}) {
543
+ const normalized = String(name || '').toLowerCase()
544
+ if (!DATASET_NAMES.has(normalized)) throw new WoolError('Unsupported Wool dataset.')
545
+
546
+ const projectId = projectIdFrom(options)
547
+ return requestJson(
548
+ `/api/projects/${encodeURIComponent(projectId)}/web/${encodeURIComponent(normalized)}`,
549
+ datasetQuery(normalized, options)
550
+ )
551
+ }
552
+
553
+ const overview = (options = {}) => dataset('overview', options)
554
+ const performance = (options = {}) => dataset('performance', options)
555
+ const revenue = (options = {}) => dataset('revenue', options)
556
+ const events = (options = {}) => dataset('events', options)
557
+ const retention = (options = {}) => dataset('retention', options)
558
+ const goals = (options = {}) => dataset('goals', options)
559
+ const funnels = (options = {}) => dataset('funnels', options)
560
+ const realtime = (options = {}) => dataset('realtime', options)
561
+
562
+ async function datasets(options = {}) {
563
+ const [
564
+ overviewResult,
565
+ performanceResult,
566
+ revenueResult,
567
+ eventsResult,
568
+ retentionResult,
569
+ goalsResult,
570
+ funnelsResult,
571
+ realtimeResult
572
+ ] = await Promise.all([
573
+ overview(options),
574
+ performance(options),
575
+ revenue(options),
576
+ events(options),
577
+ retention(options),
578
+ goals(options),
579
+ funnels(options),
580
+ realtime(options)
581
+ ])
582
+
583
+ return {
584
+ project: overviewResult.project,
585
+ overview: overviewResult.overview,
586
+ performance: performanceResult.performance,
587
+ revenue: revenueResult.revenue,
588
+ events: eventsResult.events,
589
+ retention: retentionResult.retention,
590
+ goals: goalsResult.goals,
591
+ funnels: funnelsResult.funnels,
592
+ realtime: realtimeResult.realtime
593
+ }
594
+ }
595
+
596
+ async function cards(options = {}) {
597
+ const data = await datasets(options)
598
+ return {
599
+ project: data.project,
600
+ cards: buildCards(data),
601
+ datasets: data
602
+ }
603
+ }
604
+
307
605
  function identify(retentionId, options = {}) {
308
606
  state.identity = {
309
607
  retentionId: cleanText(retentionId),
@@ -319,8 +617,21 @@ export function createClient(options = {}) {
319
617
  }
320
618
 
321
619
  return {
620
+ cards,
621
+ dataset,
622
+ datasets,
623
+ events,
624
+ funnels,
625
+ goals,
322
626
  identify,
627
+ overview,
323
628
  pageview,
629
+ performance,
630
+ project,
631
+ projects,
632
+ realtime,
633
+ retention,
634
+ revenue,
324
635
  requestContext,
325
636
  reset,
326
637
  track
@@ -387,12 +698,77 @@ export function pageview(options) {
387
698
  return requireDefaultClient().pageview(options)
388
699
  }
389
700
 
701
+ export function projects() {
702
+ return requireDefaultClient().projects()
703
+ }
704
+
705
+ export function project(options) {
706
+ return requireDefaultClient().project(options)
707
+ }
708
+
709
+ export function dataset(name, options) {
710
+ return requireDefaultClient().dataset(name, options)
711
+ }
712
+
713
+ export function datasets(options) {
714
+ return requireDefaultClient().datasets(options)
715
+ }
716
+
717
+ export function cards(options) {
718
+ return requireDefaultClient().cards(options)
719
+ }
720
+
721
+ export function overview(options) {
722
+ return requireDefaultClient().overview(options)
723
+ }
724
+
725
+ export function performance(options) {
726
+ return requireDefaultClient().performance(options)
727
+ }
728
+
729
+ export function revenue(options) {
730
+ return requireDefaultClient().revenue(options)
731
+ }
732
+
733
+ export function events(options) {
734
+ return requireDefaultClient().events(options)
735
+ }
736
+
737
+ export function retention(options) {
738
+ return requireDefaultClient().retention(options)
739
+ }
740
+
741
+ export function goals(options) {
742
+ return requireDefaultClient().goals(options)
743
+ }
744
+
745
+ export function funnels(options) {
746
+ return requireDefaultClient().funnels(options)
747
+ }
748
+
749
+ export function realtime(options) {
750
+ return requireDefaultClient().realtime(options)
751
+ }
752
+
390
753
  export const wool = {
391
754
  init,
755
+ cards,
756
+ dataset,
757
+ datasets,
758
+ events,
759
+ funnels,
760
+ goals,
392
761
  identify,
393
762
  reset,
763
+ overview,
394
764
  track,
395
765
  pageview,
766
+ performance,
767
+ project,
768
+ projects,
769
+ realtime,
770
+ retention,
771
+ revenue,
396
772
  createClient,
397
773
  requestContext,
398
774
  expressMiddleware,