@coursebuilder/analytics 1.1.0 → 1.1.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.
- package/dist/api/index.d.ts +22 -2
- package/dist/api/index.js +40 -5
- package/dist/api/index.js.map +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.js +43 -1
- package/dist/catalog.js.map +1 -1
- package/dist/components/index.d.ts +29 -0
- package/dist/components/index.js +91 -2
- package/dist/components/index.js.map +1 -1
- package/dist/engine.js +94 -6
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +94 -6
- package/dist/index.js.map +1 -1
- package/dist/providers/database.d.ts +144 -2
- package/dist/providers/database.js +652 -20
- package/dist/providers/database.js.map +1 -1
- package/dist/providers/index.js +654 -22
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/survey.d.ts +1 -1
- package/dist/providers/survey.js +2 -2
- package/dist/providers/survey.js.map +1 -1
- package/dist/types.d.ts +151 -3
- package/package.json +5 -3
- package/src/api/catalog-handler.ts +44 -2
- package/src/api/token-handler.ts +3 -2
- package/src/catalog.ts +49 -1
- package/src/components/omnibus-dashboard.tsx +163 -0
- package/src/engine.ts +66 -6
- package/src/providers/attribution-recovery.test.ts +63 -0
- package/src/providers/attribution-recovery.ts +97 -0
- package/src/providers/database.ts +812 -42
- package/src/providers/survey.ts +3 -1
- package/src/types.ts +166 -2
package/dist/engine.js
CHANGED
|
@@ -75,11 +75,18 @@ var catalog = {
|
|
|
75
75
|
},
|
|
76
76
|
"attribution/coverage": {
|
|
77
77
|
name: "attribution/coverage",
|
|
78
|
-
description: "Attributed vs dark revenue",
|
|
78
|
+
description: "Attributed vs dark paid revenue, with commerce lane context",
|
|
79
79
|
category: "attribution",
|
|
80
80
|
provider: "database",
|
|
81
81
|
fn: "getAttributedRevenueSummary"
|
|
82
82
|
},
|
|
83
|
+
"attribution/commerce-lanes": {
|
|
84
|
+
name: "attribution/commerce-lanes",
|
|
85
|
+
description: "Commerce record lanes: paid purchases, access grants, free upgrades, synthetic tests",
|
|
86
|
+
category: "attribution",
|
|
87
|
+
provider: "database",
|
|
88
|
+
fn: "getCommerceLaneSummary"
|
|
89
|
+
},
|
|
83
90
|
traffic: {
|
|
84
91
|
name: "traffic",
|
|
85
92
|
description: "GA4 traffic overview",
|
|
@@ -196,12 +203,47 @@ var catalog = {
|
|
|
196
203
|
provider: "newsletter",
|
|
197
204
|
fn: "getEmailCampaignAttribution"
|
|
198
205
|
},
|
|
206
|
+
"attribution/email-campaigns/strict": {
|
|
207
|
+
name: "attribution/email-campaigns/strict",
|
|
208
|
+
description: "Kit email broadcasts \u2192 shortlink clicks \u2192 strict purchase-field purchases and revenue per campaign",
|
|
209
|
+
category: "attribution",
|
|
210
|
+
provider: "newsletter",
|
|
211
|
+
fn: "getEmailCampaignAttributionStrict"
|
|
212
|
+
},
|
|
213
|
+
"attribution/checkout-receipt": {
|
|
214
|
+
name: "attribution/checkout-receipt",
|
|
215
|
+
description: "Read-only checkout attribution receipt for one purchase ID",
|
|
216
|
+
category: "attribution",
|
|
217
|
+
provider: "database",
|
|
218
|
+
fn: "getCheckoutAttributionReceipt"
|
|
219
|
+
},
|
|
220
|
+
"attribution/checkout-survey-fallback": {
|
|
221
|
+
name: "attribution/checkout-survey-fallback",
|
|
222
|
+
description: "Report-only dark purchase fallback from checkout survey responses",
|
|
223
|
+
category: "attribution",
|
|
224
|
+
provider: "database",
|
|
225
|
+
fn: "getCheckoutSurveyFallbackReport"
|
|
226
|
+
},
|
|
199
227
|
"correlation/survey-revenue": {
|
|
200
228
|
name: "correlation/survey-revenue",
|
|
201
229
|
description: "Survey respondents \u2192 purchase conversion by question/answer",
|
|
202
230
|
category: "correlation",
|
|
203
231
|
provider: "derived",
|
|
204
232
|
fn: "getSurveyRevenueCorrelation"
|
|
233
|
+
},
|
|
234
|
+
"correlation/survey-revenue/product": {
|
|
235
|
+
name: "correlation/survey-revenue/product",
|
|
236
|
+
description: "Product-filtered survey respondents \u2192 paid purchase conversion by question/answer",
|
|
237
|
+
category: "correlation",
|
|
238
|
+
provider: "derived",
|
|
239
|
+
fn: "getProductSurveyRevenueCorrelation"
|
|
240
|
+
},
|
|
241
|
+
"value-paths/summary": {
|
|
242
|
+
name: "value-paths/summary",
|
|
243
|
+
description: "Value Path progression, answer selection, drip fallback, and terminal completion",
|
|
244
|
+
category: "value-path",
|
|
245
|
+
provider: "database",
|
|
246
|
+
fn: "getValuePathSummary"
|
|
205
247
|
}
|
|
206
248
|
};
|
|
207
249
|
|
|
@@ -218,7 +260,8 @@ function toYouTubeRange(range) {
|
|
|
218
260
|
return range;
|
|
219
261
|
}
|
|
220
262
|
__name(toYouTubeRange, "toYouTubeRange");
|
|
221
|
-
async function invokeSurface(providers, surface,
|
|
263
|
+
async function invokeSurface(providers, surface, options) {
|
|
264
|
+
const { range, limit, offset } = options;
|
|
222
265
|
const entry = catalog[surface];
|
|
223
266
|
const provider = providers[entry.provider];
|
|
224
267
|
if (!provider) {
|
|
@@ -261,16 +304,51 @@ async function invokeSurface(providers, surface, range, limit) {
|
|
|
261
304
|
const fn = provider[entry.fn];
|
|
262
305
|
return fn(range);
|
|
263
306
|
}
|
|
264
|
-
case "attribution/
|
|
307
|
+
case "attribution/sources":
|
|
308
|
+
case "attribution/coverage":
|
|
309
|
+
case "attribution/commerce-lanes": {
|
|
310
|
+
const fn = provider[entry.fn];
|
|
311
|
+
return fn(range, {
|
|
312
|
+
productId: options.productId
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
case "attribution/email-campaigns":
|
|
316
|
+
case "attribution/email-campaigns/strict": {
|
|
317
|
+
const fn = provider[entry.fn];
|
|
318
|
+
return fn(range, limit, {
|
|
319
|
+
productId: options.productId
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
case "attribution/checkout-receipt": {
|
|
323
|
+
const fn = provider[entry.fn];
|
|
324
|
+
return fn({
|
|
325
|
+
purchaseId: options.purchaseId
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
case "attribution/checkout-survey-fallback": {
|
|
329
|
+
const fn = provider[entry.fn];
|
|
330
|
+
return fn(range, limit, {
|
|
331
|
+
productId: options.productId
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
case "surveys/questions": {
|
|
265
335
|
const fn = provider[entry.fn];
|
|
266
336
|
return fn(range, limit);
|
|
267
337
|
}
|
|
268
|
-
case "surveys/questions":
|
|
269
338
|
case "surveys/responses": {
|
|
270
339
|
const fn = provider[entry.fn];
|
|
271
|
-
return fn(range, limit);
|
|
340
|
+
return fn(range, limit, offset);
|
|
272
341
|
}
|
|
273
342
|
default: {
|
|
343
|
+
if (surface === "correlation/survey-revenue/product") {
|
|
344
|
+
const productSurveyFn = provider[entry.fn];
|
|
345
|
+
return productSurveyFn(range, limit, {
|
|
346
|
+
productId: options.productId,
|
|
347
|
+
surveyId: options.surveyId,
|
|
348
|
+
surveySlug: options.surveySlug,
|
|
349
|
+
questionId: options.questionId
|
|
350
|
+
});
|
|
351
|
+
}
|
|
274
352
|
const fn = provider[entry.fn];
|
|
275
353
|
if (surface === "attribution/content" || surface === "correlation/survey-revenue") {
|
|
276
354
|
return fn(range, limit);
|
|
@@ -284,10 +362,20 @@ function createAnalyticsEngine(providers) {
|
|
|
284
362
|
async function query(surface, options) {
|
|
285
363
|
const range = options?.range ?? "30d";
|
|
286
364
|
const limit = options?.limit ?? 20;
|
|
365
|
+
const offset = options?.offset ?? 0;
|
|
287
366
|
const entry = catalog[surface];
|
|
288
367
|
const startMs = Date.now();
|
|
289
368
|
try {
|
|
290
|
-
const data = await invokeSurface(providers, surface,
|
|
369
|
+
const data = await invokeSurface(providers, surface, {
|
|
370
|
+
range,
|
|
371
|
+
limit,
|
|
372
|
+
offset,
|
|
373
|
+
productId: options?.productId,
|
|
374
|
+
purchaseId: options?.purchaseId,
|
|
375
|
+
surveyId: options?.surveyId,
|
|
376
|
+
surveySlug: options?.surveySlug,
|
|
377
|
+
questionId: options?.questionId
|
|
378
|
+
});
|
|
291
379
|
if (data === null) {
|
|
292
380
|
return {
|
|
293
381
|
ok: false,
|
package/dist/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/catalog.ts","../src/engine.ts"],"sourcesContent":["import type { SurfaceName } from './types'\n\nexport interface SurfaceEntry {\n\tname: SurfaceName\n\tdescription: string\n\tcategory:\n\t\t| 'revenue'\n\t\t| 'attribution'\n\t\t| 'traffic'\n\t\t| 'youtube'\n\t\t| 'correlation'\n\t\t| 'survey'\n\tprovider: 'database' | 'ga4' | 'youtube' | 'derived' | 'newsletter' | 'survey'\n\tfn: string\n\tunavailableFix?: string\n}\n\nexport const catalog: Record<SurfaceName, SurfaceEntry> = {\n\tsummary: {\n\t\tname: 'summary',\n\t\tdescription: 'Revenue overview: total, purchase count, AOV',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueSummary',\n\t},\n\t'revenue/daily': {\n\t\tname: 'revenue/daily',\n\t\tdescription: 'Revenue and purchase count per day',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByDay',\n\t},\n\t'revenue/products': {\n\t\tname: 'revenue/products',\n\t\tdescription: 'Revenue grouped by product',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByProduct',\n\t},\n\t'revenue/countries': {\n\t\tname: 'revenue/countries',\n\t\tdescription: 'Revenue grouped by country',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByCountry',\n\t},\n\t'purchases/recent': {\n\t\tname: 'purchases/recent',\n\t\tdescription: 'Last N purchases',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRecentPurchases',\n\t},\n\tattribution: {\n\t\tname: 'attribution',\n\t\tdescription: 'Attribution event counts by type',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributionSummary',\n\t},\n\t'attribution/shortlinks': {\n\t\tname: 'attribution/shortlinks',\n\t\tdescription: 'Per-shortlink click performance',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getShortlinkPerformance',\n\t},\n\t'attribution/sources': {\n\t\tname: 'attribution/sources',\n\t\tdescription: 'Revenue by first-touch source/medium/campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueBySource',\n\t},\n\t'attribution/funnel': {\n\t\tname: 'attribution/funnel',\n\t\tdescription: 'Signup → purchase conversion funnel',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getConversionFunnel',\n\t},\n\t'attribution/content': {\n\t\tname: 'attribution/content',\n\t\tdescription: 'Content consumed by purchasers',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getContentPurchaseCorrelation',\n\t},\n\t'attribution/coverage': {\n\t\tname: 'attribution/coverage',\n\t\tdescription: 'Attributed vs dark revenue',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributedRevenueSummary',\n\t},\n\ttraffic: {\n\t\tname: 'traffic',\n\t\tdescription: 'GA4 traffic overview',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficOverview',\n\t},\n\t'traffic/daily': {\n\t\tname: 'traffic/daily',\n\t\tdescription: 'GA4 daily sessions',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getSessionsByDay',\n\t},\n\t'traffic/pages': {\n\t\tname: 'traffic/pages',\n\t\tdescription: 'Top pages by pageviews',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTopPages',\n\t},\n\t'traffic/sources': {\n\t\tname: 'traffic/sources',\n\t\tdescription: 'Traffic sources',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficSources',\n\t},\n\tyoutube: {\n\t\tname: 'youtube',\n\t\tdescription: 'Channel overview (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelOverview',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/videos': {\n\t\tname: 'youtube/videos',\n\t\tdescription: 'Per-video performance (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getVideoPerformance',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/daily': {\n\t\tname: 'youtube/daily',\n\t\tdescription: 'Daily views + watch minutes (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelTimeseries',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/sources': {\n\t\tname: 'youtube/sources',\n\t\tdescription: 'YouTube traffic sources (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getYouTubeTrafficSources',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'correlation/traffic-revenue': {\n\t\tname: 'correlation/traffic-revenue',\n\t\tdescription: 'GA4 sessions + revenue by day',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getTrafficRevenueCorrelation',\n\t},\n\t'correlation/youtube-revenue': {\n\t\tname: 'correlation/youtube-revenue',\n\t\tdescription: 'YouTube (≈48h lag) + GA4 + revenue overlay',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getYouTubeRevenueCorrelation',\n\t},\n\tsurveys: {\n\t\tname: 'surveys',\n\t\tdescription: 'Survey overview: total surveys, responses, respondents',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveySummary',\n\t},\n\t'surveys/list': {\n\t\tname: 'surveys/list',\n\t\tdescription: 'All surveys with response counts',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyList',\n\t},\n\t'surveys/daily': {\n\t\tname: 'surveys/daily',\n\t\tdescription: 'Daily survey response volume',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponsesByDay',\n\t},\n\t'surveys/questions': {\n\t\tname: 'surveys/questions',\n\t\tdescription: 'Top questions by response count with answer distribution',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyQuestionBreakdown',\n\t},\n\t'surveys/responses': {\n\t\tname: 'surveys/responses',\n\t\tdescription:\n\t\t\t'Individual survey responses as flat rows (multi-choice and open-ended)',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponses',\n\t},\n\t'attribution/email-campaigns': {\n\t\tname: 'attribution/email-campaigns',\n\t\tdescription:\n\t\t\t'Kit email broadcasts → shortlink clicks → signups → purchases → revenue per campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'newsletter',\n\t\tfn: 'getEmailCampaignAttribution',\n\t},\n\t'correlation/survey-revenue': {\n\t\tname: 'correlation/survey-revenue',\n\t\tdescription: 'Survey respondents → purchase conversion by question/answer',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getSurveyRevenueCorrelation',\n\t},\n} as const satisfies Record<SurfaceName, SurfaceEntry>\n\nexport const ANALYTICS_CATALOG = catalog\n","import { catalog, type SurfaceEntry } from './catalog'\nimport type {\n\tAnalyticsRange,\n\tQueryOptions,\n\tQueryResult,\n\tSurfaceMap,\n\tSurfaceName,\n} from './types'\n\nexport type AnalyticsProvider = Record<\n\tstring,\n\t((...args: any[]) => Promise<any>) | unknown\n>\n\nexport type ProviderMap = Record<string, AnalyticsProvider>\n\nfunction toGA4Range(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nfunction toYouTubeRange(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nasync function invokeSurface<S extends SurfaceName>(\n\tproviders: ProviderMap,\n\tsurface: S,\n\trange: AnalyticsRange,\n\tlimit: number,\n): Promise<SurfaceMap[S] | null> {\n\tconst entry = catalog[surface]\n\tconst provider = providers[entry.provider]\n\n\tif (!provider) {\n\t\treturn null\n\t}\n\n\t// Check that the provider actually exposes the expected function.\n\t// A provider may exist but not support every surface in the catalog\n\t// (e.g. CWA's derived provider omits YouTube correlation).\n\tif (typeof provider[entry.fn] !== 'function') {\n\t\treturn null\n\t}\n\n\tswitch (surface) {\n\t\tcase 'purchases/recent': {\n\t\t\tconst fn = provider[entry.fn] as (limit: number) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(limit)\n\t\t}\n\t\tcase 'youtube': {\n\t\t\tconst fn = provider[entry.fn] as () => Promise<SurfaceMap[S] | null>\n\t\t\treturn fn()\n\t\t}\n\t\tcase 'youtube/videos':\n\t\tcase 'youtube/daily':\n\t\tcase 'youtube/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (surface === 'youtube/videos') {\n\t\t\t\treturn fn(toYouTubeRange(range), limit)\n\t\t\t}\n\t\t\treturn fn(toYouTubeRange(range))\n\t\t}\n\t\tcase 'traffic':\n\t\tcase 'traffic/daily':\n\t\tcase 'traffic/pages':\n\t\tcase 'traffic/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\tif (surface === 'traffic/pages' || surface === 'traffic/sources') {\n\t\t\t\treturn fn(toGA4Range(range), limit)\n\t\t\t}\n\t\t\treturn fn(toGA4Range(range))\n\t\t}\n\t\tcase 'surveys':\n\t\tcase 'surveys/list':\n\t\tcase 'surveys/daily': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range)\n\t\t}\n\t\tcase 'attribution/email-campaigns': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit)\n\t\t}\n\t\tcase 'surveys/questions':\n\t\tcase 'surveys/responses': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit)\n\t\t}\n\t\tdefault: {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (\n\t\t\t\tsurface === 'attribution/content' ||\n\t\t\t\tsurface === 'correlation/survey-revenue'\n\t\t\t) {\n\t\t\t\treturn fn(range, limit)\n\t\t\t}\n\t\t\treturn fn(range)\n\t\t}\n\t}\n}\n\n/**\n * Creates an analytics engine bound to the given provider map.\n *\n * @param providers - A map of provider name → provider implementation. Keys\n * must match the `provider` field values used in the catalog\n * ('database', 'ga4', 'youtube', 'derived', 'newsletter', 'survey').\n * @returns An object with `query`, `queryMany`, and `getCatalog` methods.\n */\nexport function createAnalyticsEngine(providers: ProviderMap) {\n\tasync function query<S extends SurfaceName>(\n\t\tsurface: S,\n\t\toptions?: QueryOptions,\n\t): Promise<QueryResult<S>> {\n\t\tconst range = options?.range ?? '30d'\n\t\tconst limit = options?.limit ?? 20\n\t\tconst entry = catalog[surface]\n\t\tconst startMs = Date.now()\n\n\t\ttry {\n\t\t\tconst data = await invokeSurface(providers, surface, range, limit)\n\n\t\t\tif (data === null) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tsurface,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tmessage: `${entry.provider} is not available`,\n\t\t\t\t\t\tcode: `${entry.provider.toUpperCase()}_UNAVAILABLE`,\n\t\t\t\t\t},\n\t\t\t\t\tfix: entry.unavailableFix ?? 'Check provider configuration',\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tsurface,\n\t\t\t\trange,\n\t\t\t\tdata,\n\t\t\t\tmeta: {\n\t\t\t\t\tqueryTimeMs: Date.now() - startMs,\n\t\t\t\t\ttruncated: Array.isArray(data) && data.length >= limit,\n\t\t\t\t},\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tsurface,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\tcode: 'QUERY_FAILED',\n\t\t\t\t},\n\t\t\t\tfix: `The ${surface} query failed. Try a different range or check server logs.`,\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function queryMany<S extends SurfaceName>(\n\t\tsurfaces: S[],\n\t\toptions?: QueryOptions,\n\t): Promise<{ [K in S]: QueryResult<K> }> {\n\t\tconst results = await Promise.all(\n\t\t\tsurfaces.map(\n\t\t\t\tasync (surface) => [surface, await query(surface, options)] as const,\n\t\t\t),\n\t\t)\n\n\t\treturn Object.fromEntries(results) as { [K in S]: QueryResult<K> }\n\t}\n\n\t/**\n\t * Returns catalog entries filtered to only surfaces that this engine\n\t * can actually serve (provider present and function exported).\n\t */\n\tfunction getCatalog(): SurfaceEntry[] {\n\t\treturn Object.values(catalog).filter((entry) => {\n\t\t\tconst provider = providers[entry.provider]\n\t\t\treturn provider && typeof provider[entry.fn] === 'function'\n\t\t})\n\t}\n\n\treturn { query, queryMany, getCatalog }\n}\n"],"mappings":"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","names":["catalog","summary","name","description","category","provider","fn","attribution","traffic","youtube","unavailableFix","surveys","toGA4Range","range","toYouTubeRange","invokeSurface","providers","surface","limit","entry","catalog","provider","fn","createAnalyticsEngine","query","options","startMs","Date","now","data","ok","error","message","code","toUpperCase","fix","unavailableFix","meta","queryTimeMs","truncated","Array","isArray","length","Error","String","queryMany","surfaces","results","Promise","all","map","Object","fromEntries","getCatalog","values","filter"]}
|
|
1
|
+
{"version":3,"sources":["../src/catalog.ts","../src/engine.ts"],"sourcesContent":["import type { SurfaceName } from './types'\n\nexport interface SurfaceEntry {\n\tname: SurfaceName\n\tdescription: string\n\tcategory:\n\t\t| 'revenue'\n\t\t| 'attribution'\n\t\t| 'traffic'\n\t\t| 'youtube'\n\t\t| 'correlation'\n\t\t| 'survey'\n\t\t| 'value-path'\n\tprovider: 'database' | 'ga4' | 'youtube' | 'derived' | 'newsletter' | 'survey'\n\tfn: string\n\tunavailableFix?: string\n}\n\nexport const catalog: Record<SurfaceName, SurfaceEntry> = {\n\tsummary: {\n\t\tname: 'summary',\n\t\tdescription: 'Revenue overview: total, purchase count, AOV',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueSummary',\n\t},\n\t'revenue/daily': {\n\t\tname: 'revenue/daily',\n\t\tdescription: 'Revenue and purchase count per day',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByDay',\n\t},\n\t'revenue/products': {\n\t\tname: 'revenue/products',\n\t\tdescription: 'Revenue grouped by product',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByProduct',\n\t},\n\t'revenue/countries': {\n\t\tname: 'revenue/countries',\n\t\tdescription: 'Revenue grouped by country',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByCountry',\n\t},\n\t'purchases/recent': {\n\t\tname: 'purchases/recent',\n\t\tdescription: 'Last N purchases',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRecentPurchases',\n\t},\n\tattribution: {\n\t\tname: 'attribution',\n\t\tdescription: 'Attribution event counts by type',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributionSummary',\n\t},\n\t'attribution/shortlinks': {\n\t\tname: 'attribution/shortlinks',\n\t\tdescription: 'Per-shortlink click performance',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getShortlinkPerformance',\n\t},\n\t'attribution/sources': {\n\t\tname: 'attribution/sources',\n\t\tdescription: 'Revenue by first-touch source/medium/campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueBySource',\n\t},\n\t'attribution/funnel': {\n\t\tname: 'attribution/funnel',\n\t\tdescription: 'Signup → purchase conversion funnel',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getConversionFunnel',\n\t},\n\t'attribution/content': {\n\t\tname: 'attribution/content',\n\t\tdescription: 'Content consumed by purchasers',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getContentPurchaseCorrelation',\n\t},\n\t'attribution/coverage': {\n\t\tname: 'attribution/coverage',\n\t\tdescription: 'Attributed vs dark paid revenue, with commerce lane context',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributedRevenueSummary',\n\t},\n\t'attribution/commerce-lanes': {\n\t\tname: 'attribution/commerce-lanes',\n\t\tdescription:\n\t\t\t'Commerce record lanes: paid purchases, access grants, free upgrades, synthetic tests',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getCommerceLaneSummary',\n\t},\n\ttraffic: {\n\t\tname: 'traffic',\n\t\tdescription: 'GA4 traffic overview',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficOverview',\n\t},\n\t'traffic/daily': {\n\t\tname: 'traffic/daily',\n\t\tdescription: 'GA4 daily sessions',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getSessionsByDay',\n\t},\n\t'traffic/pages': {\n\t\tname: 'traffic/pages',\n\t\tdescription: 'Top pages by pageviews',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTopPages',\n\t},\n\t'traffic/sources': {\n\t\tname: 'traffic/sources',\n\t\tdescription: 'Traffic sources',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficSources',\n\t},\n\tyoutube: {\n\t\tname: 'youtube',\n\t\tdescription: 'Channel overview (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelOverview',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/videos': {\n\t\tname: 'youtube/videos',\n\t\tdescription: 'Per-video performance (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getVideoPerformance',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/daily': {\n\t\tname: 'youtube/daily',\n\t\tdescription: 'Daily views + watch minutes (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelTimeseries',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/sources': {\n\t\tname: 'youtube/sources',\n\t\tdescription: 'YouTube traffic sources (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getYouTubeTrafficSources',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'correlation/traffic-revenue': {\n\t\tname: 'correlation/traffic-revenue',\n\t\tdescription: 'GA4 sessions + revenue by day',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getTrafficRevenueCorrelation',\n\t},\n\t'correlation/youtube-revenue': {\n\t\tname: 'correlation/youtube-revenue',\n\t\tdescription: 'YouTube (≈48h lag) + GA4 + revenue overlay',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getYouTubeRevenueCorrelation',\n\t},\n\tsurveys: {\n\t\tname: 'surveys',\n\t\tdescription: 'Survey overview: total surveys, responses, respondents',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveySummary',\n\t},\n\t'surveys/list': {\n\t\tname: 'surveys/list',\n\t\tdescription: 'All surveys with response counts',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyList',\n\t},\n\t'surveys/daily': {\n\t\tname: 'surveys/daily',\n\t\tdescription: 'Daily survey response volume',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponsesByDay',\n\t},\n\t'surveys/questions': {\n\t\tname: 'surveys/questions',\n\t\tdescription: 'Top questions by response count with answer distribution',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyQuestionBreakdown',\n\t},\n\t'surveys/responses': {\n\t\tname: 'surveys/responses',\n\t\tdescription:\n\t\t\t'Individual survey responses as flat rows (multi-choice and open-ended)',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponses',\n\t},\n\t'attribution/email-campaigns': {\n\t\tname: 'attribution/email-campaigns',\n\t\tdescription:\n\t\t\t'Kit email broadcasts → shortlink clicks → signups → purchases → revenue per campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'newsletter',\n\t\tfn: 'getEmailCampaignAttribution',\n\t},\n\t'attribution/email-campaigns/strict': {\n\t\tname: 'attribution/email-campaigns/strict',\n\t\tdescription:\n\t\t\t'Kit email broadcasts → shortlink clicks → strict purchase-field purchases and revenue per campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'newsletter',\n\t\tfn: 'getEmailCampaignAttributionStrict',\n\t},\n\t'attribution/checkout-receipt': {\n\t\tname: 'attribution/checkout-receipt',\n\t\tdescription: 'Read-only checkout attribution receipt for one purchase ID',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getCheckoutAttributionReceipt',\n\t},\n\t'attribution/checkout-survey-fallback': {\n\t\tname: 'attribution/checkout-survey-fallback',\n\t\tdescription:\n\t\t\t'Report-only dark purchase fallback from checkout survey responses',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getCheckoutSurveyFallbackReport',\n\t},\n\t'correlation/survey-revenue': {\n\t\tname: 'correlation/survey-revenue',\n\t\tdescription: 'Survey respondents → purchase conversion by question/answer',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getSurveyRevenueCorrelation',\n\t},\n\t'correlation/survey-revenue/product': {\n\t\tname: 'correlation/survey-revenue/product',\n\t\tdescription:\n\t\t\t'Product-filtered survey respondents → paid purchase conversion by question/answer',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getProductSurveyRevenueCorrelation',\n\t},\n\t'value-paths/summary': {\n\t\tname: 'value-paths/summary',\n\t\tdescription:\n\t\t\t'Value Path progression, answer selection, drip fallback, and terminal completion',\n\t\tcategory: 'value-path',\n\t\tprovider: 'database',\n\t\tfn: 'getValuePathSummary',\n\t},\n} as const satisfies Record<SurfaceName, SurfaceEntry>\n\nexport const ANALYTICS_CATALOG = catalog\n","import { catalog, type SurfaceEntry } from './catalog'\nimport type {\n\tAnalyticsRange,\n\tQueryOptions,\n\tQueryResult,\n\tSurfaceMap,\n\tSurfaceName,\n} from './types'\n\nexport type AnalyticsProvider = Record<\n\tstring,\n\t((...args: any[]) => Promise<any>) | unknown\n>\n\nexport type ProviderMap = Record<string, AnalyticsProvider>\n\nfunction toGA4Range(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nfunction toYouTubeRange(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nasync function invokeSurface<S extends SurfaceName>(\n\tproviders: ProviderMap,\n\tsurface: S,\n\toptions: Required<Pick<QueryOptions, 'range' | 'limit' | 'offset'>> &\n\t\tOmit<QueryOptions, 'range' | 'limit' | 'offset'>,\n): Promise<SurfaceMap[S] | null> {\n\tconst { range, limit, offset } = options\n\tconst entry = catalog[surface]\n\tconst provider = providers[entry.provider]\n\n\tif (!provider) {\n\t\treturn null\n\t}\n\n\t// Check that the provider actually exposes the expected function.\n\t// A provider may exist but not support every surface in the catalog\n\t// (e.g. CWA's derived provider omits YouTube correlation).\n\tif (typeof provider[entry.fn] !== 'function') {\n\t\treturn null\n\t}\n\n\tswitch (surface) {\n\t\tcase 'purchases/recent': {\n\t\t\tconst fn = provider[entry.fn] as (limit: number) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(limit)\n\t\t}\n\t\tcase 'youtube': {\n\t\t\tconst fn = provider[entry.fn] as () => Promise<SurfaceMap[S] | null>\n\t\t\treturn fn()\n\t\t}\n\t\tcase 'youtube/videos':\n\t\tcase 'youtube/daily':\n\t\tcase 'youtube/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (surface === 'youtube/videos') {\n\t\t\t\treturn fn(toYouTubeRange(range), limit)\n\t\t\t}\n\t\t\treturn fn(toYouTubeRange(range))\n\t\t}\n\t\tcase 'traffic':\n\t\tcase 'traffic/daily':\n\t\tcase 'traffic/pages':\n\t\tcase 'traffic/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\tif (surface === 'traffic/pages' || surface === 'traffic/sources') {\n\t\t\t\treturn fn(toGA4Range(range), limit)\n\t\t\t}\n\t\t\treturn fn(toGA4Range(range))\n\t\t}\n\t\tcase 'surveys':\n\t\tcase 'surveys/list':\n\t\tcase 'surveys/daily': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range)\n\t\t}\n\t\tcase 'attribution/sources':\n\t\tcase 'attribution/coverage':\n\t\tcase 'attribution/commerce-lanes': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tfilters?: Pick<QueryOptions, 'productId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, { productId: options.productId })\n\t\t}\n\t\tcase 'attribution/email-campaigns':\n\t\tcase 'attribution/email-campaigns/strict': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t\tfilters?: Pick<QueryOptions, 'productId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit, { productId: options.productId })\n\t\t}\n\t\tcase 'attribution/checkout-receipt': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\tfilters: Pick<QueryOptions, 'purchaseId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn({ purchaseId: options.purchaseId })\n\t\t}\n\t\tcase 'attribution/checkout-survey-fallback': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t\tfilters?: Pick<QueryOptions, 'productId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit, { productId: options.productId })\n\t\t}\n\t\tcase 'surveys/questions': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit)\n\t\t}\n\t\tcase 'surveys/responses': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit: number,\n\t\t\t\toffset: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit, offset)\n\t\t}\n\t\tdefault: {\n\t\t\tif (surface === 'correlation/survey-revenue/product') {\n\t\t\t\tconst productSurveyFn = provider[entry.fn] as (\n\t\t\t\t\trange: AnalyticsRange,\n\t\t\t\t\tlimit?: number,\n\t\t\t\t\tfilters?: Pick<\n\t\t\t\t\t\tQueryOptions,\n\t\t\t\t\t\t'productId' | 'surveyId' | 'surveySlug' | 'questionId'\n\t\t\t\t\t>,\n\t\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\t\treturn productSurveyFn(range, limit, {\n\t\t\t\t\tproductId: options.productId,\n\t\t\t\t\tsurveyId: options.surveyId,\n\t\t\t\t\tsurveySlug: options.surveySlug,\n\t\t\t\t\tquestionId: options.questionId,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (\n\t\t\t\tsurface === 'attribution/content' ||\n\t\t\t\tsurface === 'correlation/survey-revenue'\n\t\t\t) {\n\t\t\t\treturn fn(range, limit)\n\t\t\t}\n\t\t\treturn fn(range)\n\t\t}\n\t}\n}\n\n/**\n * Creates an analytics engine bound to the given provider map.\n *\n * @param providers - A map of provider name → provider implementation. Keys\n * must match the `provider` field values used in the catalog\n * ('database', 'ga4', 'youtube', 'derived', 'newsletter', 'survey').\n * @returns An object with `query`, `queryMany`, and `getCatalog` methods.\n */\nexport function createAnalyticsEngine(providers: ProviderMap) {\n\tasync function query<S extends SurfaceName>(\n\t\tsurface: S,\n\t\toptions?: QueryOptions,\n\t): Promise<QueryResult<S>> {\n\t\tconst range = options?.range ?? '30d'\n\t\tconst limit = options?.limit ?? 20\n\t\tconst offset = options?.offset ?? 0\n\t\tconst entry = catalog[surface]\n\t\tconst startMs = Date.now()\n\n\t\ttry {\n\t\t\tconst data = await invokeSurface(providers, surface, {\n\t\t\t\trange,\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tproductId: options?.productId,\n\t\t\t\tpurchaseId: options?.purchaseId,\n\t\t\t\tsurveyId: options?.surveyId,\n\t\t\t\tsurveySlug: options?.surveySlug,\n\t\t\t\tquestionId: options?.questionId,\n\t\t\t})\n\n\t\t\tif (data === null) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tsurface,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tmessage: `${entry.provider} is not available`,\n\t\t\t\t\t\tcode: `${entry.provider.toUpperCase()}_UNAVAILABLE`,\n\t\t\t\t\t},\n\t\t\t\t\tfix: entry.unavailableFix ?? 'Check provider configuration',\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tsurface,\n\t\t\t\trange,\n\t\t\t\tdata,\n\t\t\t\tmeta: {\n\t\t\t\t\tqueryTimeMs: Date.now() - startMs,\n\t\t\t\t\ttruncated: Array.isArray(data) && data.length >= limit,\n\t\t\t\t},\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tsurface,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\tcode: 'QUERY_FAILED',\n\t\t\t\t},\n\t\t\t\tfix: `The ${surface} query failed. Try a different range or check server logs.`,\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function queryMany<S extends SurfaceName>(\n\t\tsurfaces: S[],\n\t\toptions?: QueryOptions,\n\t): Promise<{ [K in S]: QueryResult<K> }> {\n\t\tconst results = await Promise.all(\n\t\t\tsurfaces.map(\n\t\t\t\tasync (surface) => [surface, await query(surface, options)] as const,\n\t\t\t),\n\t\t)\n\n\t\treturn Object.fromEntries(results) as { [K in S]: QueryResult<K> }\n\t}\n\n\t/**\n\t * Returns catalog entries filtered to only surfaces that this engine\n\t * can actually serve (provider present and function exported).\n\t */\n\tfunction getCatalog(): SurfaceEntry[] {\n\t\treturn Object.values(catalog).filter((entry) => {\n\t\t\tconst provider = providers[entry.provider]\n\t\t\treturn provider && typeof provider[entry.fn] === 'function'\n\t\t})\n\t}\n\n\treturn { query, queryMany, getCatalog }\n}\n"],"mappings":"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","names":["catalog","summary","name","description","category","provider","fn","attribution","traffic","youtube","unavailableFix","surveys","toGA4Range","range","toYouTubeRange","invokeSurface","providers","surface","options","limit","offset","entry","catalog","provider","fn","productId","purchaseId","productSurveyFn","surveyId","surveySlug","questionId","createAnalyticsEngine","query","startMs","Date","now","data","ok","error","message","code","toUpperCase","fix","unavailableFix","meta","queryTimeMs","truncated","Array","isArray","length","Error","String","queryMany","surfaces","results","Promise","all","map","Object","fromEntries","getCatalog","values","filter"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { AnalyticsRange, AttributionCount, AttributionCoverage, ContentCorrelation, ConversionFunnel, Count, EmailCampaignFunnel, EmailCampaignKitLink, EmailCampaignShortlink, EmailRevenueOverview, Minutes, Percentage, QueryOptions, QueryResult, RecentPurchase, RevenueByCountry, RevenueByProduct, RevenueBySource, RevenueDaily, RevenueSummary, Seconds, ShortlinkPerformance, SurfaceMap, SurfaceName, SurveyConversionByQuestion, SurveyListItem, SurveyQuestionBreakdown, SurveyResponseRow, SurveyResponsesDaily, SurveyRevenueCorrelation, SurveySummary, TopPage, TrafficDaily, TrafficOverview, TrafficRevenueCorrelation, TrafficSource, USD, YouTubeChannelOverview, YouTubeDaily, YouTubeRevenueCorrelation, YouTubeTrafficSource, YouTubeVideoPerformance } from './types.js';
|
|
1
|
+
export { AnalyticsRange, AttributionCount, AttributionCoverage, AttributionSignalSummary, CheckoutAttributionReceipt, CheckoutSurveyFallbackAnswer, CheckoutSurveyFallbackReport, CommerceLaneSummary, CommerceRecordKind, ContentCorrelation, ConversionFunnel, Count, EmailCampaignFunnel, EmailCampaignKitLink, EmailCampaignShortlink, EmailRevenueOverview, Minutes, Percentage, ProductSurveyRevenueAnswer, ProductSurveyRevenueCorrelation, QueryOptions, QueryResult, RecentPurchase, RevenueByCountry, RevenueByProduct, RevenueBySource, RevenueDaily, RevenueSummary, Seconds, ShortlinkPerformance, SurfaceMap, SurfaceName, SurveyConversionByQuestion, SurveyListItem, SurveyQuestionBreakdown, SurveyResponseRow, SurveyResponsesDaily, SurveyRevenueCorrelation, SurveySummary, TopPage, TrafficDaily, TrafficOverview, TrafficRevenueCorrelation, TrafficSource, USD, ValuePathAnswerBucket, ValuePathStepBucket, ValuePathSummary, ValuePathTerminalBucket, YouTubeChannelOverview, YouTubeDaily, YouTubeRevenueCorrelation, YouTubeTrafficSource, YouTubeVideoPerformance } from './types.js';
|
|
2
2
|
export { ANALYTICS_CATALOG, SurfaceEntry, catalog } from './catalog.js';
|
|
3
3
|
export { AnalyticsProvider, ProviderMap, createAnalyticsEngine } from './engine.js';
|
package/dist/index.js
CHANGED
|
@@ -75,11 +75,18 @@ var catalog = {
|
|
|
75
75
|
},
|
|
76
76
|
"attribution/coverage": {
|
|
77
77
|
name: "attribution/coverage",
|
|
78
|
-
description: "Attributed vs dark revenue",
|
|
78
|
+
description: "Attributed vs dark paid revenue, with commerce lane context",
|
|
79
79
|
category: "attribution",
|
|
80
80
|
provider: "database",
|
|
81
81
|
fn: "getAttributedRevenueSummary"
|
|
82
82
|
},
|
|
83
|
+
"attribution/commerce-lanes": {
|
|
84
|
+
name: "attribution/commerce-lanes",
|
|
85
|
+
description: "Commerce record lanes: paid purchases, access grants, free upgrades, synthetic tests",
|
|
86
|
+
category: "attribution",
|
|
87
|
+
provider: "database",
|
|
88
|
+
fn: "getCommerceLaneSummary"
|
|
89
|
+
},
|
|
83
90
|
traffic: {
|
|
84
91
|
name: "traffic",
|
|
85
92
|
description: "GA4 traffic overview",
|
|
@@ -196,12 +203,47 @@ var catalog = {
|
|
|
196
203
|
provider: "newsletter",
|
|
197
204
|
fn: "getEmailCampaignAttribution"
|
|
198
205
|
},
|
|
206
|
+
"attribution/email-campaigns/strict": {
|
|
207
|
+
name: "attribution/email-campaigns/strict",
|
|
208
|
+
description: "Kit email broadcasts \u2192 shortlink clicks \u2192 strict purchase-field purchases and revenue per campaign",
|
|
209
|
+
category: "attribution",
|
|
210
|
+
provider: "newsletter",
|
|
211
|
+
fn: "getEmailCampaignAttributionStrict"
|
|
212
|
+
},
|
|
213
|
+
"attribution/checkout-receipt": {
|
|
214
|
+
name: "attribution/checkout-receipt",
|
|
215
|
+
description: "Read-only checkout attribution receipt for one purchase ID",
|
|
216
|
+
category: "attribution",
|
|
217
|
+
provider: "database",
|
|
218
|
+
fn: "getCheckoutAttributionReceipt"
|
|
219
|
+
},
|
|
220
|
+
"attribution/checkout-survey-fallback": {
|
|
221
|
+
name: "attribution/checkout-survey-fallback",
|
|
222
|
+
description: "Report-only dark purchase fallback from checkout survey responses",
|
|
223
|
+
category: "attribution",
|
|
224
|
+
provider: "database",
|
|
225
|
+
fn: "getCheckoutSurveyFallbackReport"
|
|
226
|
+
},
|
|
199
227
|
"correlation/survey-revenue": {
|
|
200
228
|
name: "correlation/survey-revenue",
|
|
201
229
|
description: "Survey respondents \u2192 purchase conversion by question/answer",
|
|
202
230
|
category: "correlation",
|
|
203
231
|
provider: "derived",
|
|
204
232
|
fn: "getSurveyRevenueCorrelation"
|
|
233
|
+
},
|
|
234
|
+
"correlation/survey-revenue/product": {
|
|
235
|
+
name: "correlation/survey-revenue/product",
|
|
236
|
+
description: "Product-filtered survey respondents \u2192 paid purchase conversion by question/answer",
|
|
237
|
+
category: "correlation",
|
|
238
|
+
provider: "derived",
|
|
239
|
+
fn: "getProductSurveyRevenueCorrelation"
|
|
240
|
+
},
|
|
241
|
+
"value-paths/summary": {
|
|
242
|
+
name: "value-paths/summary",
|
|
243
|
+
description: "Value Path progression, answer selection, drip fallback, and terminal completion",
|
|
244
|
+
category: "value-path",
|
|
245
|
+
provider: "database",
|
|
246
|
+
fn: "getValuePathSummary"
|
|
205
247
|
}
|
|
206
248
|
};
|
|
207
249
|
var ANALYTICS_CATALOG = catalog;
|
|
@@ -219,7 +261,8 @@ function toYouTubeRange(range) {
|
|
|
219
261
|
return range;
|
|
220
262
|
}
|
|
221
263
|
__name(toYouTubeRange, "toYouTubeRange");
|
|
222
|
-
async function invokeSurface(providers, surface,
|
|
264
|
+
async function invokeSurface(providers, surface, options) {
|
|
265
|
+
const { range, limit, offset } = options;
|
|
223
266
|
const entry = catalog[surface];
|
|
224
267
|
const provider = providers[entry.provider];
|
|
225
268
|
if (!provider) {
|
|
@@ -262,16 +305,51 @@ async function invokeSurface(providers, surface, range, limit) {
|
|
|
262
305
|
const fn = provider[entry.fn];
|
|
263
306
|
return fn(range);
|
|
264
307
|
}
|
|
265
|
-
case "attribution/
|
|
308
|
+
case "attribution/sources":
|
|
309
|
+
case "attribution/coverage":
|
|
310
|
+
case "attribution/commerce-lanes": {
|
|
311
|
+
const fn = provider[entry.fn];
|
|
312
|
+
return fn(range, {
|
|
313
|
+
productId: options.productId
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
case "attribution/email-campaigns":
|
|
317
|
+
case "attribution/email-campaigns/strict": {
|
|
318
|
+
const fn = provider[entry.fn];
|
|
319
|
+
return fn(range, limit, {
|
|
320
|
+
productId: options.productId
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
case "attribution/checkout-receipt": {
|
|
324
|
+
const fn = provider[entry.fn];
|
|
325
|
+
return fn({
|
|
326
|
+
purchaseId: options.purchaseId
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
case "attribution/checkout-survey-fallback": {
|
|
330
|
+
const fn = provider[entry.fn];
|
|
331
|
+
return fn(range, limit, {
|
|
332
|
+
productId: options.productId
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
case "surveys/questions": {
|
|
266
336
|
const fn = provider[entry.fn];
|
|
267
337
|
return fn(range, limit);
|
|
268
338
|
}
|
|
269
|
-
case "surveys/questions":
|
|
270
339
|
case "surveys/responses": {
|
|
271
340
|
const fn = provider[entry.fn];
|
|
272
|
-
return fn(range, limit);
|
|
341
|
+
return fn(range, limit, offset);
|
|
273
342
|
}
|
|
274
343
|
default: {
|
|
344
|
+
if (surface === "correlation/survey-revenue/product") {
|
|
345
|
+
const productSurveyFn = provider[entry.fn];
|
|
346
|
+
return productSurveyFn(range, limit, {
|
|
347
|
+
productId: options.productId,
|
|
348
|
+
surveyId: options.surveyId,
|
|
349
|
+
surveySlug: options.surveySlug,
|
|
350
|
+
questionId: options.questionId
|
|
351
|
+
});
|
|
352
|
+
}
|
|
275
353
|
const fn = provider[entry.fn];
|
|
276
354
|
if (surface === "attribution/content" || surface === "correlation/survey-revenue") {
|
|
277
355
|
return fn(range, limit);
|
|
@@ -285,10 +363,20 @@ function createAnalyticsEngine(providers) {
|
|
|
285
363
|
async function query(surface, options) {
|
|
286
364
|
const range = options?.range ?? "30d";
|
|
287
365
|
const limit = options?.limit ?? 20;
|
|
366
|
+
const offset = options?.offset ?? 0;
|
|
288
367
|
const entry = catalog[surface];
|
|
289
368
|
const startMs = Date.now();
|
|
290
369
|
try {
|
|
291
|
-
const data = await invokeSurface(providers, surface,
|
|
370
|
+
const data = await invokeSurface(providers, surface, {
|
|
371
|
+
range,
|
|
372
|
+
limit,
|
|
373
|
+
offset,
|
|
374
|
+
productId: options?.productId,
|
|
375
|
+
purchaseId: options?.purchaseId,
|
|
376
|
+
surveyId: options?.surveyId,
|
|
377
|
+
surveySlug: options?.surveySlug,
|
|
378
|
+
questionId: options?.questionId
|
|
379
|
+
});
|
|
292
380
|
if (data === null) {
|
|
293
381
|
return {
|
|
294
382
|
ok: false,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/catalog.ts","../src/engine.ts"],"sourcesContent":["import type { SurfaceName } from './types'\n\nexport interface SurfaceEntry {\n\tname: SurfaceName\n\tdescription: string\n\tcategory:\n\t\t| 'revenue'\n\t\t| 'attribution'\n\t\t| 'traffic'\n\t\t| 'youtube'\n\t\t| 'correlation'\n\t\t| 'survey'\n\tprovider: 'database' | 'ga4' | 'youtube' | 'derived' | 'newsletter' | 'survey'\n\tfn: string\n\tunavailableFix?: string\n}\n\nexport const catalog: Record<SurfaceName, SurfaceEntry> = {\n\tsummary: {\n\t\tname: 'summary',\n\t\tdescription: 'Revenue overview: total, purchase count, AOV',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueSummary',\n\t},\n\t'revenue/daily': {\n\t\tname: 'revenue/daily',\n\t\tdescription: 'Revenue and purchase count per day',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByDay',\n\t},\n\t'revenue/products': {\n\t\tname: 'revenue/products',\n\t\tdescription: 'Revenue grouped by product',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByProduct',\n\t},\n\t'revenue/countries': {\n\t\tname: 'revenue/countries',\n\t\tdescription: 'Revenue grouped by country',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByCountry',\n\t},\n\t'purchases/recent': {\n\t\tname: 'purchases/recent',\n\t\tdescription: 'Last N purchases',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRecentPurchases',\n\t},\n\tattribution: {\n\t\tname: 'attribution',\n\t\tdescription: 'Attribution event counts by type',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributionSummary',\n\t},\n\t'attribution/shortlinks': {\n\t\tname: 'attribution/shortlinks',\n\t\tdescription: 'Per-shortlink click performance',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getShortlinkPerformance',\n\t},\n\t'attribution/sources': {\n\t\tname: 'attribution/sources',\n\t\tdescription: 'Revenue by first-touch source/medium/campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueBySource',\n\t},\n\t'attribution/funnel': {\n\t\tname: 'attribution/funnel',\n\t\tdescription: 'Signup → purchase conversion funnel',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getConversionFunnel',\n\t},\n\t'attribution/content': {\n\t\tname: 'attribution/content',\n\t\tdescription: 'Content consumed by purchasers',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getContentPurchaseCorrelation',\n\t},\n\t'attribution/coverage': {\n\t\tname: 'attribution/coverage',\n\t\tdescription: 'Attributed vs dark revenue',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributedRevenueSummary',\n\t},\n\ttraffic: {\n\t\tname: 'traffic',\n\t\tdescription: 'GA4 traffic overview',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficOverview',\n\t},\n\t'traffic/daily': {\n\t\tname: 'traffic/daily',\n\t\tdescription: 'GA4 daily sessions',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getSessionsByDay',\n\t},\n\t'traffic/pages': {\n\t\tname: 'traffic/pages',\n\t\tdescription: 'Top pages by pageviews',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTopPages',\n\t},\n\t'traffic/sources': {\n\t\tname: 'traffic/sources',\n\t\tdescription: 'Traffic sources',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficSources',\n\t},\n\tyoutube: {\n\t\tname: 'youtube',\n\t\tdescription: 'Channel overview (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelOverview',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/videos': {\n\t\tname: 'youtube/videos',\n\t\tdescription: 'Per-video performance (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getVideoPerformance',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/daily': {\n\t\tname: 'youtube/daily',\n\t\tdescription: 'Daily views + watch minutes (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelTimeseries',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/sources': {\n\t\tname: 'youtube/sources',\n\t\tdescription: 'YouTube traffic sources (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getYouTubeTrafficSources',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'correlation/traffic-revenue': {\n\t\tname: 'correlation/traffic-revenue',\n\t\tdescription: 'GA4 sessions + revenue by day',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getTrafficRevenueCorrelation',\n\t},\n\t'correlation/youtube-revenue': {\n\t\tname: 'correlation/youtube-revenue',\n\t\tdescription: 'YouTube (≈48h lag) + GA4 + revenue overlay',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getYouTubeRevenueCorrelation',\n\t},\n\tsurveys: {\n\t\tname: 'surveys',\n\t\tdescription: 'Survey overview: total surveys, responses, respondents',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveySummary',\n\t},\n\t'surveys/list': {\n\t\tname: 'surveys/list',\n\t\tdescription: 'All surveys with response counts',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyList',\n\t},\n\t'surveys/daily': {\n\t\tname: 'surveys/daily',\n\t\tdescription: 'Daily survey response volume',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponsesByDay',\n\t},\n\t'surveys/questions': {\n\t\tname: 'surveys/questions',\n\t\tdescription: 'Top questions by response count with answer distribution',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyQuestionBreakdown',\n\t},\n\t'surveys/responses': {\n\t\tname: 'surveys/responses',\n\t\tdescription:\n\t\t\t'Individual survey responses as flat rows (multi-choice and open-ended)',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponses',\n\t},\n\t'attribution/email-campaigns': {\n\t\tname: 'attribution/email-campaigns',\n\t\tdescription:\n\t\t\t'Kit email broadcasts → shortlink clicks → signups → purchases → revenue per campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'newsletter',\n\t\tfn: 'getEmailCampaignAttribution',\n\t},\n\t'correlation/survey-revenue': {\n\t\tname: 'correlation/survey-revenue',\n\t\tdescription: 'Survey respondents → purchase conversion by question/answer',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getSurveyRevenueCorrelation',\n\t},\n} as const satisfies Record<SurfaceName, SurfaceEntry>\n\nexport const ANALYTICS_CATALOG = catalog\n","import { catalog, type SurfaceEntry } from './catalog'\nimport type {\n\tAnalyticsRange,\n\tQueryOptions,\n\tQueryResult,\n\tSurfaceMap,\n\tSurfaceName,\n} from './types'\n\nexport type AnalyticsProvider = Record<\n\tstring,\n\t((...args: any[]) => Promise<any>) | unknown\n>\n\nexport type ProviderMap = Record<string, AnalyticsProvider>\n\nfunction toGA4Range(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nfunction toYouTubeRange(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nasync function invokeSurface<S extends SurfaceName>(\n\tproviders: ProviderMap,\n\tsurface: S,\n\trange: AnalyticsRange,\n\tlimit: number,\n): Promise<SurfaceMap[S] | null> {\n\tconst entry = catalog[surface]\n\tconst provider = providers[entry.provider]\n\n\tif (!provider) {\n\t\treturn null\n\t}\n\n\t// Check that the provider actually exposes the expected function.\n\t// A provider may exist but not support every surface in the catalog\n\t// (e.g. CWA's derived provider omits YouTube correlation).\n\tif (typeof provider[entry.fn] !== 'function') {\n\t\treturn null\n\t}\n\n\tswitch (surface) {\n\t\tcase 'purchases/recent': {\n\t\t\tconst fn = provider[entry.fn] as (limit: number) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(limit)\n\t\t}\n\t\tcase 'youtube': {\n\t\t\tconst fn = provider[entry.fn] as () => Promise<SurfaceMap[S] | null>\n\t\t\treturn fn()\n\t\t}\n\t\tcase 'youtube/videos':\n\t\tcase 'youtube/daily':\n\t\tcase 'youtube/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (surface === 'youtube/videos') {\n\t\t\t\treturn fn(toYouTubeRange(range), limit)\n\t\t\t}\n\t\t\treturn fn(toYouTubeRange(range))\n\t\t}\n\t\tcase 'traffic':\n\t\tcase 'traffic/daily':\n\t\tcase 'traffic/pages':\n\t\tcase 'traffic/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\tif (surface === 'traffic/pages' || surface === 'traffic/sources') {\n\t\t\t\treturn fn(toGA4Range(range), limit)\n\t\t\t}\n\t\t\treturn fn(toGA4Range(range))\n\t\t}\n\t\tcase 'surveys':\n\t\tcase 'surveys/list':\n\t\tcase 'surveys/daily': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range)\n\t\t}\n\t\tcase 'attribution/email-campaigns': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit)\n\t\t}\n\t\tcase 'surveys/questions':\n\t\tcase 'surveys/responses': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit)\n\t\t}\n\t\tdefault: {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (\n\t\t\t\tsurface === 'attribution/content' ||\n\t\t\t\tsurface === 'correlation/survey-revenue'\n\t\t\t) {\n\t\t\t\treturn fn(range, limit)\n\t\t\t}\n\t\t\treturn fn(range)\n\t\t}\n\t}\n}\n\n/**\n * Creates an analytics engine bound to the given provider map.\n *\n * @param providers - A map of provider name → provider implementation. Keys\n * must match the `provider` field values used in the catalog\n * ('database', 'ga4', 'youtube', 'derived', 'newsletter', 'survey').\n * @returns An object with `query`, `queryMany`, and `getCatalog` methods.\n */\nexport function createAnalyticsEngine(providers: ProviderMap) {\n\tasync function query<S extends SurfaceName>(\n\t\tsurface: S,\n\t\toptions?: QueryOptions,\n\t): Promise<QueryResult<S>> {\n\t\tconst range = options?.range ?? '30d'\n\t\tconst limit = options?.limit ?? 20\n\t\tconst entry = catalog[surface]\n\t\tconst startMs = Date.now()\n\n\t\ttry {\n\t\t\tconst data = await invokeSurface(providers, surface, range, limit)\n\n\t\t\tif (data === null) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tsurface,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tmessage: `${entry.provider} is not available`,\n\t\t\t\t\t\tcode: `${entry.provider.toUpperCase()}_UNAVAILABLE`,\n\t\t\t\t\t},\n\t\t\t\t\tfix: entry.unavailableFix ?? 'Check provider configuration',\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tsurface,\n\t\t\t\trange,\n\t\t\t\tdata,\n\t\t\t\tmeta: {\n\t\t\t\t\tqueryTimeMs: Date.now() - startMs,\n\t\t\t\t\ttruncated: Array.isArray(data) && data.length >= limit,\n\t\t\t\t},\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tsurface,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\tcode: 'QUERY_FAILED',\n\t\t\t\t},\n\t\t\t\tfix: `The ${surface} query failed. Try a different range or check server logs.`,\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function queryMany<S extends SurfaceName>(\n\t\tsurfaces: S[],\n\t\toptions?: QueryOptions,\n\t): Promise<{ [K in S]: QueryResult<K> }> {\n\t\tconst results = await Promise.all(\n\t\t\tsurfaces.map(\n\t\t\t\tasync (surface) => [surface, await query(surface, options)] as const,\n\t\t\t),\n\t\t)\n\n\t\treturn Object.fromEntries(results) as { [K in S]: QueryResult<K> }\n\t}\n\n\t/**\n\t * Returns catalog entries filtered to only surfaces that this engine\n\t * can actually serve (provider present and function exported).\n\t */\n\tfunction getCatalog(): SurfaceEntry[] {\n\t\treturn Object.values(catalog).filter((entry) => {\n\t\t\tconst provider = providers[entry.provider]\n\t\t\treturn provider && typeof provider[entry.fn] === 'function'\n\t\t})\n\t}\n\n\treturn { query, queryMany, getCatalog }\n}\n"],"mappings":";;;;AAiBO,IAAMA,UAA6C;EACzDC,SAAS;IACRC,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,iBAAiB;IAChBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,oBAAoB;IACnBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,qBAAqB;IACpBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,oBAAoB;IACnBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACAC,aAAa;IACZL,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,0BAA0B;IACzBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,uBAAuB;IACtBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,sBAAsB;IACrBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,uBAAuB;IACtBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,wBAAwB;IACvBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACAE,SAAS;IACRN,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,iBAAiB;IAChBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,iBAAiB;IAChBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,mBAAmB;IAClBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACAG,SAAS;IACRP,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;IACJI,gBAAgB;EACjB;EACA,kBAAkB;IACjBR,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;IACJI,gBAAgB;EACjB;EACA,iBAAiB;IAChBR,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;IACJI,gBAAgB;EACjB;EACA,mBAAmB;IAClBR,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;IACJI,gBAAgB;EACjB;EACA,+BAA+B;IAC9BR,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,+BAA+B;IAC9BJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACAK,SAAS;IACRT,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,gBAAgB;IACfJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,iBAAiB;IAChBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,qBAAqB;IACpBJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,qBAAqB;IACpBJ,MAAM;IACNC,aACC;IACDC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,+BAA+B;IAC9BJ,MAAM;IACNC,aACC;IACDC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;EACA,8BAA8B;IAC7BJ,MAAM;IACNC,aAAa;IACbC,UAAU;IACVC,UAAU;IACVC,IAAI;EACL;AACD;AAEO,IAAMM,oBAAoBZ;;;AC9MjC,SAASa,WAAWC,OAAqB;AACxC,MAAIA,UAAU;AAAO,WAAO;AAC5B,SAAOA;AACR;AAHSD;AAKT,SAASE,eAAeD,OAAqB;AAC5C,MAAIA,UAAU;AAAO,WAAO;AAC5B,SAAOA;AACR;AAHSC;AAKT,eAAeC,cACdC,WACAC,SACAJ,OACAK,OAAa;AAEb,QAAMC,QAAQC,QAAQH,OAAAA;AACtB,QAAMI,WAAWL,UAAUG,MAAME,QAAQ;AAEzC,MAAI,CAACA,UAAU;AACd,WAAO;EACR;AAKA,MAAI,OAAOA,SAASF,MAAMG,EAAE,MAAM,YAAY;AAC7C,WAAO;EACR;AAEA,UAAQL,SAAAA;IACP,KAAK,oBAAoB;AACxB,YAAMK,KAAKD,SAASF,MAAMG,EAAE;AAC5B,aAAOA,GAAGJ,KAAAA;IACX;IACA,KAAK,WAAW;AACf,YAAMI,KAAKD,SAASF,MAAMG,EAAE;AAC5B,aAAOA,GAAAA;IACR;IACA,KAAK;IACL,KAAK;IACL,KAAK,mBAAmB;AACvB,YAAMA,KAAKD,SAASF,MAAMG,EAAE;AAI5B,UAAIL,YAAY,kBAAkB;AACjC,eAAOK,GAAGR,eAAeD,KAAAA,GAAQK,KAAAA;MAClC;AACA,aAAOI,GAAGR,eAAeD,KAAAA,CAAAA;IAC1B;IACA,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK,mBAAmB;AACvB,YAAMS,KAAKD,SAASF,MAAMG,EAAE;AAI5B,UAAIL,YAAY,mBAAmBA,YAAY,mBAAmB;AACjE,eAAOK,GAAGV,WAAWC,KAAAA,GAAQK,KAAAA;MAC9B;AACA,aAAOI,GAAGV,WAAWC,KAAAA,CAAAA;IACtB;IACA,KAAK;IACL,KAAK;IACL,KAAK,iBAAiB;AACrB,YAAMS,KAAKD,SAASF,MAAMG,EAAE;AAG5B,aAAOA,GAAGT,KAAAA;IACX;IACA,KAAK,+BAA+B;AACnC,YAAMS,KAAKD,SAASF,MAAMG,EAAE;AAI5B,aAAOA,GAAGT,OAAOK,KAAAA;IAClB;IACA,KAAK;IACL,KAAK,qBAAqB;AACzB,YAAMI,KAAKD,SAASF,MAAMG,EAAE;AAI5B,aAAOA,GAAGT,OAAOK,KAAAA;IAClB;IACA,SAAS;AACR,YAAMI,KAAKD,SAASF,MAAMG,EAAE;AAI5B,UACCL,YAAY,yBACZA,YAAY,8BACX;AACD,eAAOK,GAAGT,OAAOK,KAAAA;MAClB;AACA,aAAOI,GAAGT,KAAAA;IACX;EACD;AACD;AA3FeE;AAqGR,SAASQ,sBAAsBP,WAAsB;AAC3D,iBAAeQ,MACdP,SACAQ,SAAsB;AAEtB,UAAMZ,QAAQY,SAASZ,SAAS;AAChC,UAAMK,QAAQO,SAASP,SAAS;AAChC,UAAMC,QAAQC,QAAQH,OAAAA;AACtB,UAAMS,UAAUC,KAAKC,IAAG;AAExB,QAAI;AACH,YAAMC,OAAO,MAAMd,cAAcC,WAAWC,SAASJ,OAAOK,KAAAA;AAE5D,UAAIW,SAAS,MAAM;AAClB,eAAO;UACNC,IAAI;UACJb;UACAc,OAAO;YACNC,SAAS,GAAGb,MAAME,QAAQ;YAC1BY,MAAM,GAAGd,MAAME,SAASa,YAAW,CAAA;UACpC;UACAC,KAAKhB,MAAMiB,kBAAkB;QAC9B;MACD;AAEA,aAAO;QACNN,IAAI;QACJb;QACAJ;QACAgB;QACAQ,MAAM;UACLC,aAAaX,KAAKC,IAAG,IAAKF;UAC1Ba,WAAWC,MAAMC,QAAQZ,IAAAA,KAASA,KAAKa,UAAUxB;QAClD;MACD;IACD,SAASa,OAAO;AACf,aAAO;QACND,IAAI;QACJb;QACAc,OAAO;UACNC,SAASD,iBAAiBY,QAAQZ,MAAMC,UAAUY,OAAOb,KAAAA;UACzDE,MAAM;QACP;QACAE,KAAK,OAAOlB,OAAAA;MACb;IACD;EACD;AA7CeO;AA+Cf,iBAAeqB,UACdC,UACArB,SAAsB;AAEtB,UAAMsB,UAAU,MAAMC,QAAQC,IAC7BH,SAASI,IACR,OAAOjC,YAAY;MAACA;MAAS,MAAMO,MAAMP,SAASQ,OAAAA;KAAS,CAAA;AAI7D,WAAO0B,OAAOC,YAAYL,OAAAA;EAC3B;AAXeF;AAiBf,WAASQ,aAAAA;AACR,WAAOF,OAAOG,OAAOlC,OAAAA,EAASmC,OAAO,CAACpC,UAAAA;AACrC,YAAME,WAAWL,UAAUG,MAAME,QAAQ;AACzC,aAAOA,YAAY,OAAOA,SAASF,MAAMG,EAAE,MAAM;IAClD,CAAA;EACD;AALS+B;AAOT,SAAO;IAAE7B;IAAOqB;IAAWQ;EAAW;AACvC;AAzEgB9B;","names":["catalog","summary","name","description","category","provider","fn","attribution","traffic","youtube","unavailableFix","surveys","ANALYTICS_CATALOG","toGA4Range","range","toYouTubeRange","invokeSurface","providers","surface","limit","entry","catalog","provider","fn","createAnalyticsEngine","query","options","startMs","Date","now","data","ok","error","message","code","toUpperCase","fix","unavailableFix","meta","queryTimeMs","truncated","Array","isArray","length","Error","String","queryMany","surfaces","results","Promise","all","map","Object","fromEntries","getCatalog","values","filter"]}
|
|
1
|
+
{"version":3,"sources":["../src/catalog.ts","../src/engine.ts"],"sourcesContent":["import type { SurfaceName } from './types'\n\nexport interface SurfaceEntry {\n\tname: SurfaceName\n\tdescription: string\n\tcategory:\n\t\t| 'revenue'\n\t\t| 'attribution'\n\t\t| 'traffic'\n\t\t| 'youtube'\n\t\t| 'correlation'\n\t\t| 'survey'\n\t\t| 'value-path'\n\tprovider: 'database' | 'ga4' | 'youtube' | 'derived' | 'newsletter' | 'survey'\n\tfn: string\n\tunavailableFix?: string\n}\n\nexport const catalog: Record<SurfaceName, SurfaceEntry> = {\n\tsummary: {\n\t\tname: 'summary',\n\t\tdescription: 'Revenue overview: total, purchase count, AOV',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueSummary',\n\t},\n\t'revenue/daily': {\n\t\tname: 'revenue/daily',\n\t\tdescription: 'Revenue and purchase count per day',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByDay',\n\t},\n\t'revenue/products': {\n\t\tname: 'revenue/products',\n\t\tdescription: 'Revenue grouped by product',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByProduct',\n\t},\n\t'revenue/countries': {\n\t\tname: 'revenue/countries',\n\t\tdescription: 'Revenue grouped by country',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueByCountry',\n\t},\n\t'purchases/recent': {\n\t\tname: 'purchases/recent',\n\t\tdescription: 'Last N purchases',\n\t\tcategory: 'revenue',\n\t\tprovider: 'database',\n\t\tfn: 'getRecentPurchases',\n\t},\n\tattribution: {\n\t\tname: 'attribution',\n\t\tdescription: 'Attribution event counts by type',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributionSummary',\n\t},\n\t'attribution/shortlinks': {\n\t\tname: 'attribution/shortlinks',\n\t\tdescription: 'Per-shortlink click performance',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getShortlinkPerformance',\n\t},\n\t'attribution/sources': {\n\t\tname: 'attribution/sources',\n\t\tdescription: 'Revenue by first-touch source/medium/campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getRevenueBySource',\n\t},\n\t'attribution/funnel': {\n\t\tname: 'attribution/funnel',\n\t\tdescription: 'Signup → purchase conversion funnel',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getConversionFunnel',\n\t},\n\t'attribution/content': {\n\t\tname: 'attribution/content',\n\t\tdescription: 'Content consumed by purchasers',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getContentPurchaseCorrelation',\n\t},\n\t'attribution/coverage': {\n\t\tname: 'attribution/coverage',\n\t\tdescription: 'Attributed vs dark paid revenue, with commerce lane context',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getAttributedRevenueSummary',\n\t},\n\t'attribution/commerce-lanes': {\n\t\tname: 'attribution/commerce-lanes',\n\t\tdescription:\n\t\t\t'Commerce record lanes: paid purchases, access grants, free upgrades, synthetic tests',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getCommerceLaneSummary',\n\t},\n\ttraffic: {\n\t\tname: 'traffic',\n\t\tdescription: 'GA4 traffic overview',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficOverview',\n\t},\n\t'traffic/daily': {\n\t\tname: 'traffic/daily',\n\t\tdescription: 'GA4 daily sessions',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getSessionsByDay',\n\t},\n\t'traffic/pages': {\n\t\tname: 'traffic/pages',\n\t\tdescription: 'Top pages by pageviews',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTopPages',\n\t},\n\t'traffic/sources': {\n\t\tname: 'traffic/sources',\n\t\tdescription: 'Traffic sources',\n\t\tcategory: 'traffic',\n\t\tprovider: 'ga4',\n\t\tfn: 'getTrafficSources',\n\t},\n\tyoutube: {\n\t\tname: 'youtube',\n\t\tdescription: 'Channel overview (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelOverview',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/videos': {\n\t\tname: 'youtube/videos',\n\t\tdescription: 'Per-video performance (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getVideoPerformance',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/daily': {\n\t\tname: 'youtube/daily',\n\t\tdescription: 'Daily views + watch minutes (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getChannelTimeseries',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'youtube/sources': {\n\t\tname: 'youtube/sources',\n\t\tdescription: 'YouTube traffic sources (≈48h lag)',\n\t\tcategory: 'youtube',\n\t\tprovider: 'youtube',\n\t\tfn: 'getYouTubeTrafficSources',\n\t\tunavailableFix: 'Complete OAuth at /api/analytics/youtube-auth',\n\t},\n\t'correlation/traffic-revenue': {\n\t\tname: 'correlation/traffic-revenue',\n\t\tdescription: 'GA4 sessions + revenue by day',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getTrafficRevenueCorrelation',\n\t},\n\t'correlation/youtube-revenue': {\n\t\tname: 'correlation/youtube-revenue',\n\t\tdescription: 'YouTube (≈48h lag) + GA4 + revenue overlay',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getYouTubeRevenueCorrelation',\n\t},\n\tsurveys: {\n\t\tname: 'surveys',\n\t\tdescription: 'Survey overview: total surveys, responses, respondents',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveySummary',\n\t},\n\t'surveys/list': {\n\t\tname: 'surveys/list',\n\t\tdescription: 'All surveys with response counts',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyList',\n\t},\n\t'surveys/daily': {\n\t\tname: 'surveys/daily',\n\t\tdescription: 'Daily survey response volume',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponsesByDay',\n\t},\n\t'surveys/questions': {\n\t\tname: 'surveys/questions',\n\t\tdescription: 'Top questions by response count with answer distribution',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyQuestionBreakdown',\n\t},\n\t'surveys/responses': {\n\t\tname: 'surveys/responses',\n\t\tdescription:\n\t\t\t'Individual survey responses as flat rows (multi-choice and open-ended)',\n\t\tcategory: 'survey',\n\t\tprovider: 'survey',\n\t\tfn: 'getSurveyResponses',\n\t},\n\t'attribution/email-campaigns': {\n\t\tname: 'attribution/email-campaigns',\n\t\tdescription:\n\t\t\t'Kit email broadcasts → shortlink clicks → signups → purchases → revenue per campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'newsletter',\n\t\tfn: 'getEmailCampaignAttribution',\n\t},\n\t'attribution/email-campaigns/strict': {\n\t\tname: 'attribution/email-campaigns/strict',\n\t\tdescription:\n\t\t\t'Kit email broadcasts → shortlink clicks → strict purchase-field purchases and revenue per campaign',\n\t\tcategory: 'attribution',\n\t\tprovider: 'newsletter',\n\t\tfn: 'getEmailCampaignAttributionStrict',\n\t},\n\t'attribution/checkout-receipt': {\n\t\tname: 'attribution/checkout-receipt',\n\t\tdescription: 'Read-only checkout attribution receipt for one purchase ID',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getCheckoutAttributionReceipt',\n\t},\n\t'attribution/checkout-survey-fallback': {\n\t\tname: 'attribution/checkout-survey-fallback',\n\t\tdescription:\n\t\t\t'Report-only dark purchase fallback from checkout survey responses',\n\t\tcategory: 'attribution',\n\t\tprovider: 'database',\n\t\tfn: 'getCheckoutSurveyFallbackReport',\n\t},\n\t'correlation/survey-revenue': {\n\t\tname: 'correlation/survey-revenue',\n\t\tdescription: 'Survey respondents → purchase conversion by question/answer',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getSurveyRevenueCorrelation',\n\t},\n\t'correlation/survey-revenue/product': {\n\t\tname: 'correlation/survey-revenue/product',\n\t\tdescription:\n\t\t\t'Product-filtered survey respondents → paid purchase conversion by question/answer',\n\t\tcategory: 'correlation',\n\t\tprovider: 'derived',\n\t\tfn: 'getProductSurveyRevenueCorrelation',\n\t},\n\t'value-paths/summary': {\n\t\tname: 'value-paths/summary',\n\t\tdescription:\n\t\t\t'Value Path progression, answer selection, drip fallback, and terminal completion',\n\t\tcategory: 'value-path',\n\t\tprovider: 'database',\n\t\tfn: 'getValuePathSummary',\n\t},\n} as const satisfies Record<SurfaceName, SurfaceEntry>\n\nexport const ANALYTICS_CATALOG = catalog\n","import { catalog, type SurfaceEntry } from './catalog'\nimport type {\n\tAnalyticsRange,\n\tQueryOptions,\n\tQueryResult,\n\tSurfaceMap,\n\tSurfaceName,\n} from './types'\n\nexport type AnalyticsProvider = Record<\n\tstring,\n\t((...args: any[]) => Promise<any>) | unknown\n>\n\nexport type ProviderMap = Record<string, AnalyticsProvider>\n\nfunction toGA4Range(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nfunction toYouTubeRange(range: AnalyticsRange): '24h' | '7d' | '30d' | '90d' {\n\tif (range === 'all') return '90d'\n\treturn range\n}\n\nasync function invokeSurface<S extends SurfaceName>(\n\tproviders: ProviderMap,\n\tsurface: S,\n\toptions: Required<Pick<QueryOptions, 'range' | 'limit' | 'offset'>> &\n\t\tOmit<QueryOptions, 'range' | 'limit' | 'offset'>,\n): Promise<SurfaceMap[S] | null> {\n\tconst { range, limit, offset } = options\n\tconst entry = catalog[surface]\n\tconst provider = providers[entry.provider]\n\n\tif (!provider) {\n\t\treturn null\n\t}\n\n\t// Check that the provider actually exposes the expected function.\n\t// A provider may exist but not support every surface in the catalog\n\t// (e.g. CWA's derived provider omits YouTube correlation).\n\tif (typeof provider[entry.fn] !== 'function') {\n\t\treturn null\n\t}\n\n\tswitch (surface) {\n\t\tcase 'purchases/recent': {\n\t\t\tconst fn = provider[entry.fn] as (limit: number) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(limit)\n\t\t}\n\t\tcase 'youtube': {\n\t\t\tconst fn = provider[entry.fn] as () => Promise<SurfaceMap[S] | null>\n\t\t\treturn fn()\n\t\t}\n\t\tcase 'youtube/videos':\n\t\tcase 'youtube/daily':\n\t\tcase 'youtube/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (surface === 'youtube/videos') {\n\t\t\t\treturn fn(toYouTubeRange(range), limit)\n\t\t\t}\n\t\t\treturn fn(toYouTubeRange(range))\n\t\t}\n\t\tcase 'traffic':\n\t\tcase 'traffic/daily':\n\t\tcase 'traffic/pages':\n\t\tcase 'traffic/sources': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: '24h' | '7d' | '30d' | '90d',\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\tif (surface === 'traffic/pages' || surface === 'traffic/sources') {\n\t\t\t\treturn fn(toGA4Range(range), limit)\n\t\t\t}\n\t\t\treturn fn(toGA4Range(range))\n\t\t}\n\t\tcase 'surveys':\n\t\tcase 'surveys/list':\n\t\tcase 'surveys/daily': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range)\n\t\t}\n\t\tcase 'attribution/sources':\n\t\tcase 'attribution/coverage':\n\t\tcase 'attribution/commerce-lanes': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tfilters?: Pick<QueryOptions, 'productId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, { productId: options.productId })\n\t\t}\n\t\tcase 'attribution/email-campaigns':\n\t\tcase 'attribution/email-campaigns/strict': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t\tfilters?: Pick<QueryOptions, 'productId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit, { productId: options.productId })\n\t\t}\n\t\tcase 'attribution/checkout-receipt': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\tfilters: Pick<QueryOptions, 'purchaseId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn({ purchaseId: options.purchaseId })\n\t\t}\n\t\tcase 'attribution/checkout-survey-fallback': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t\tfilters?: Pick<QueryOptions, 'productId'>,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit, { productId: options.productId })\n\t\t}\n\t\tcase 'surveys/questions': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit)\n\t\t}\n\t\tcase 'surveys/responses': {\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit: number,\n\t\t\t\toffset: number,\n\t\t\t) => Promise<SurfaceMap[S]>\n\t\t\treturn fn(range, limit, offset)\n\t\t}\n\t\tdefault: {\n\t\t\tif (surface === 'correlation/survey-revenue/product') {\n\t\t\t\tconst productSurveyFn = provider[entry.fn] as (\n\t\t\t\t\trange: AnalyticsRange,\n\t\t\t\t\tlimit?: number,\n\t\t\t\t\tfilters?: Pick<\n\t\t\t\t\t\tQueryOptions,\n\t\t\t\t\t\t'productId' | 'surveyId' | 'surveySlug' | 'questionId'\n\t\t\t\t\t>,\n\t\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\t\treturn productSurveyFn(range, limit, {\n\t\t\t\t\tproductId: options.productId,\n\t\t\t\t\tsurveyId: options.surveyId,\n\t\t\t\t\tsurveySlug: options.surveySlug,\n\t\t\t\t\tquestionId: options.questionId,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst fn = provider[entry.fn] as (\n\t\t\t\trange: AnalyticsRange,\n\t\t\t\tlimit?: number,\n\t\t\t) => Promise<SurfaceMap[S] | null>\n\t\t\tif (\n\t\t\t\tsurface === 'attribution/content' ||\n\t\t\t\tsurface === 'correlation/survey-revenue'\n\t\t\t) {\n\t\t\t\treturn fn(range, limit)\n\t\t\t}\n\t\t\treturn fn(range)\n\t\t}\n\t}\n}\n\n/**\n * Creates an analytics engine bound to the given provider map.\n *\n * @param providers - A map of provider name → provider implementation. Keys\n * must match the `provider` field values used in the catalog\n * ('database', 'ga4', 'youtube', 'derived', 'newsletter', 'survey').\n * @returns An object with `query`, `queryMany`, and `getCatalog` methods.\n */\nexport function createAnalyticsEngine(providers: ProviderMap) {\n\tasync function query<S extends SurfaceName>(\n\t\tsurface: S,\n\t\toptions?: QueryOptions,\n\t): Promise<QueryResult<S>> {\n\t\tconst range = options?.range ?? '30d'\n\t\tconst limit = options?.limit ?? 20\n\t\tconst offset = options?.offset ?? 0\n\t\tconst entry = catalog[surface]\n\t\tconst startMs = Date.now()\n\n\t\ttry {\n\t\t\tconst data = await invokeSurface(providers, surface, {\n\t\t\t\trange,\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tproductId: options?.productId,\n\t\t\t\tpurchaseId: options?.purchaseId,\n\t\t\t\tsurveyId: options?.surveyId,\n\t\t\t\tsurveySlug: options?.surveySlug,\n\t\t\t\tquestionId: options?.questionId,\n\t\t\t})\n\n\t\t\tif (data === null) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\tsurface,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tmessage: `${entry.provider} is not available`,\n\t\t\t\t\t\tcode: `${entry.provider.toUpperCase()}_UNAVAILABLE`,\n\t\t\t\t\t},\n\t\t\t\t\tfix: entry.unavailableFix ?? 'Check provider configuration',\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tsurface,\n\t\t\t\trange,\n\t\t\t\tdata,\n\t\t\t\tmeta: {\n\t\t\t\t\tqueryTimeMs: Date.now() - startMs,\n\t\t\t\t\ttruncated: Array.isArray(data) && data.length >= limit,\n\t\t\t\t},\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tsurface,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\tcode: 'QUERY_FAILED',\n\t\t\t\t},\n\t\t\t\tfix: `The ${surface} query failed. Try a different range or check server logs.`,\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function queryMany<S extends SurfaceName>(\n\t\tsurfaces: S[],\n\t\toptions?: QueryOptions,\n\t): Promise<{ [K in S]: QueryResult<K> }> {\n\t\tconst results = await Promise.all(\n\t\t\tsurfaces.map(\n\t\t\t\tasync (surface) => [surface, await query(surface, options)] as const,\n\t\t\t),\n\t\t)\n\n\t\treturn Object.fromEntries(results) as { [K in S]: QueryResult<K> }\n\t}\n\n\t/**\n\t * Returns catalog entries filtered to only surfaces that this engine\n\t * can actually serve (provider present and function exported).\n\t */\n\tfunction getCatalog(): SurfaceEntry[] {\n\t\treturn Object.values(catalog).filter((entry) => {\n\t\t\tconst provider = providers[entry.provider]\n\t\t\treturn provider && typeof provider[entry.fn] === 'function'\n\t\t})\n\t}\n\n\treturn { query, queryMany, getCatalog }\n}\n"],"mappings":"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","names":["catalog","summary","name","description","category","provider","fn","attribution","traffic","youtube","unavailableFix","surveys","ANALYTICS_CATALOG","toGA4Range","range","toYouTubeRange","invokeSurface","providers","surface","options","limit","offset","entry","catalog","provider","fn","productId","purchaseId","productSurveyFn","surveyId","surveySlug","questionId","createAnalyticsEngine","query","startMs","Date","now","data","ok","error","message","code","toUpperCase","fix","unavailableFix","meta","queryTimeMs","truncated","Array","isArray","length","Error","String","queryMany","surfaces","results","Promise","all","map","Object","fromEntries","getCatalog","values","filter"]}
|