a tool for shared writing and social publishing
at refactor/domain-management 257 lines 8.2 kB view raw
1/** 2 * Tinybird Definitions 3 * 4 * Datasource matching the Vercel Web Analytics drain schema, 5 * endpoint pipes for publication analytics, and typed client. 6 * 7 * Column names use camelCase to match the JSON keys sent by 8 * Vercel's analytics drain (NDJSON format). 9 */ 10 11import { 12 defineDatasource, 13 defineEndpoint, 14 Tinybird, 15 node, 16 t, 17 p, 18 engine, 19 type InferRow, 20 type InferParams, 21 type InferOutputRow, 22 TokenDefinition, 23} from "@tinybirdco/sdk"; 24 25const PROD_READ_TOKEN = { name: "prod_read_token_v1", scopes: ["READ"] }; 26 27// ============================================================================ 28// Datasources 29// ============================================================================ 30 31/** 32 * Vercel Web Analytics drain events. 33 * Column names match the Vercel drain JSON keys exactly. 34 * `timestamp` is stored as UInt64 (Unix millis) as sent by Vercel. 35 */ 36export const analyticsEvents = defineDatasource("analytics_events", { 37 description: "Vercel Web Analytics drain events", 38 schema: { 39 timestamp: t.uint64(), 40 eventType: t.string().lowCardinality(), 41 eventName: t.string().default(""), 42 eventData: t.string().default(""), 43 sessionId: t.uint64(), 44 deviceId: t.uint64(), 45 origin: t.string(), 46 path: t.string(), 47 referrer: t.string().default(""), 48 queryParams: t.string().default(""), 49 route: t.string().default(""), 50 country: t.string().lowCardinality().default(""), 51 region: t.string().default(""), 52 city: t.string().default(""), 53 osName: t.string().lowCardinality().default(""), 54 osVersion: t.string().default(""), 55 clientName: t.string().lowCardinality().default(""), 56 clientType: t.string().lowCardinality().default(""), 57 clientVersion: t.string().default(""), 58 deviceType: t.string().lowCardinality().default(""), 59 deviceBrand: t.string().default(""), 60 deviceModel: t.string().default(""), 61 browserEngine: t.string().default(""), 62 browserEngineVersion: t.string().default(""), 63 sdkVersion: t.string().default(""), 64 sdkName: t.string().default(""), 65 sdkVersionFull: t.string().default(""), 66 vercelEnvironment: t.string().lowCardinality().default(""), 67 vercelUrl: t.string().default(""), 68 flags: t.string().default(""), 69 deployment: t.string().default(""), 70 schema: t.string().default(""), 71 projectId: t.string().default(""), 72 ownerId: t.string().default(""), 73 dataSourceName: t.string().default(""), 74 }, 75 engine: engine.mergeTree({ 76 sortingKey: ["origin", "timestamp"], 77 partitionKey: "toYYYYMM(fromUnixTimestamp64Milli(timestamp))", 78 }), 79}); 80 81export type AnalyticsEventsRow = InferRow<typeof analyticsEvents>; 82 83// ============================================================================ 84// Endpoints 85// ============================================================================ 86 87/** 88 * publication_traffic – daily pageview time series for a publication domain. 89 */ 90export const publicationTraffic = defineEndpoint("publication_traffic", { 91 description: "Daily pageview time series for a publication domain", 92 params: { 93 domains: p.string(), 94 date_from: p.string().optional(), 95 date_to: p.string().optional(), 96 path: p.string().optional(), 97 referrer_host: p.string().optional(), 98 }, 99 tokens: [PROD_READ_TOKEN], 100 nodes: [ 101 node({ 102 name: "endpoint", 103 sql: ` 104 SELECT 105 toDate(fromUnixTimestamp64Milli(timestamp)) AS day, 106 count() AS pageviews, 107 uniq(deviceId) AS visitors 108 FROM analytics_events 109 WHERE eventType = 'pageview' 110 AND domain(origin) IN splitByChar(',', {{String(domains)}}) 111 {% if defined(date_from) %} 112 AND fromUnixTimestamp64Milli(timestamp) >= parseDateTimeBestEffort({{String(date_from)}}) 113 {% end %} 114 {% if defined(date_to) %} 115 AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}}) 116 {% end %} 117 {% if defined(path) %} 118 AND path = {{String(path)}} 119 {% end %} 120 {% if defined(referrer_host) %} 121 AND domain(referrer) = {{String(referrer_host)}} 122 {% end %} 123 GROUP BY day 124 ORDER BY day ASC 125 `, 126 }), 127 ], 128 output: { 129 day: t.date(), 130 pageviews: t.uint64(), 131 visitors: t.uint64(), 132 }, 133}); 134 135export type PublicationTrafficParams = InferParams<typeof publicationTraffic>; 136export type PublicationTrafficOutput = InferOutputRow< 137 typeof publicationTraffic 138>; 139 140/** 141 * publication_top_referrers – top referring domains for a publication. 142 */ 143export const publicationTopReferrers = defineEndpoint( 144 "publication_top_referrers", 145 { 146 tokens: [PROD_READ_TOKEN], 147 description: "Top referrers for a publication domain", 148 params: { 149 domains: p.string(), 150 date_from: p.string().optional(), 151 date_to: p.string().optional(), 152 path: p.string().optional(), 153 referrer_host: p.string().optional(), 154 limit: p.int32().optional(10), 155 }, 156 nodes: [ 157 node({ 158 name: "endpoint", 159 sql: ` 160 SELECT 161 domain(referrer) AS referrer_host, 162 count() AS pageviews 163 FROM analytics_events 164 WHERE eventType = 'pageview' 165 AND domain(origin) IN splitByChar(',', {{String(domains)}}) 166 AND referrer != '' 167 AND domain(referrer) NOT IN splitByChar(',', {{String(domains)}}) 168 {% if defined(date_from) %} 169 AND fromUnixTimestamp64Milli(timestamp) >= parseDateTimeBestEffort({{String(date_from)}}) 170 {% end %} 171 {% if defined(date_to) %} 172 AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}}) 173 {% end %} 174 {% if defined(path) %} 175 AND path = {{String(path)}} 176 {% end %} 177 {% if defined(referrer_host) %} 178 AND domain(referrer) = {{String(referrer_host)}} 179 {% end %} 180 GROUP BY referrer_host 181 ORDER BY pageviews DESC 182 LIMIT {{Int32(limit, 10)}} 183 `, 184 }), 185 ], 186 output: { 187 referrer_host: t.string(), 188 pageviews: t.uint64(), 189 }, 190 }, 191); 192 193export type PublicationTopReferrersParams = InferParams< 194 typeof publicationTopReferrers 195>; 196export type PublicationTopReferrersOutput = InferOutputRow< 197 typeof publicationTopReferrers 198>; 199 200/** 201 * publication_top_pages – top pages by pageviews for a publication. 202 */ 203export const publicationTopPages = defineEndpoint("publication_top_pages", { 204 description: "Top pages for a publication domain", 205 tokens: [PROD_READ_TOKEN], 206 params: { 207 domains: p.string(), 208 date_from: p.string().optional(), 209 date_to: p.string().optional(), 210 referrer_host: p.string().optional(), 211 limit: p.int32().optional(10), 212 }, 213 nodes: [ 214 node({ 215 name: "endpoint", 216 sql: ` 217 SELECT 218 path, 219 count() AS pageviews 220 FROM analytics_events 221 WHERE eventType = 'pageview' 222 AND domain(origin) IN splitByChar(',', {{String(domains)}}) 223 {% if defined(date_from) %} 224 AND fromUnixTimestamp64Milli(timestamp) >= parseDateTimeBestEffort({{String(date_from)}}) 225 {% end %} 226 {% if defined(date_to) %} 227 AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}}) 228 {% end %} 229 {% if defined(referrer_host) %} 230 AND domain(referrer) = {{String(referrer_host)}} 231 {% end %} 232 GROUP BY path 233 ORDER BY pageviews DESC 234 LIMIT {{Int32(limit, 10)}} 235 `, 236 }), 237 ], 238 output: { 239 path: t.string(), 240 pageviews: t.uint64(), 241 }, 242}); 243 244export type PublicationTopPagesParams = InferParams<typeof publicationTopPages>; 245export type PublicationTopPagesOutput = InferOutputRow< 246 typeof publicationTopPages 247>; 248 249// ============================================================================ 250// Client 251// ============================================================================ 252 253export const tinybird = new Tinybird({ 254 datasources: { analyticsEvents }, 255 pipes: { publicationTraffic, publicationTopReferrers, publicationTopPages }, 256 devMode: false, 257});