a tool for shared writing and social publishing

Wire up Tinybird analytics to publication dashboard with Recharts

Add real data fetching via SWR for traffic, referrers, and subscriber
timeseries. Replace placeholder chart divs with Recharts AreaCharts,
wire up live referrer data, and fix PostSelector to pass URL paths
instead of titles to the analytics API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+1570 -60
+78
app/api/rpc/[command]/get_publication_analytics.ts
··· 1 + import { z } from "zod"; 2 + import { makeRoute } from "../lib"; 3 + import type { Env } from "./route"; 4 + import { getIdentityData } from "actions/getIdentityData"; 5 + import { tinybird } from "lib/tinybird"; 6 + 7 + export type GetPublicationAnalyticsReturnType = Awaited< 8 + ReturnType<(typeof get_publication_analytics)["handler"]> 9 + >; 10 + 11 + export const get_publication_analytics = makeRoute({ 12 + route: "get_publication_analytics", 13 + input: z.object({ 14 + publication_uri: z.string(), 15 + from: z.string().optional(), 16 + to: z.string().optional(), 17 + path: z.string().optional(), 18 + }), 19 + handler: async ( 20 + { publication_uri, from, to, path }, 21 + { supabase }: Pick<Env, "supabase">, 22 + ) => { 23 + const identity = await getIdentityData(); 24 + if (!identity?.atp_did || !identity.entitlements?.publication_analytics) { 25 + return { error: "unauthorized" as const }; 26 + } 27 + 28 + // Verify the user owns this publication 29 + const { data: publication } = await supabase 30 + .from("publications") 31 + .select("*, publication_domains(*)") 32 + .eq("uri", publication_uri) 33 + .single(); 34 + 35 + if (!publication || publication.identity_did !== identity.atp_did) { 36 + return { error: "not_found" as const }; 37 + } 38 + 39 + const domain = publication.publication_domains?.[0]?.domain; 40 + if (!domain) { 41 + return { 42 + result: { traffic: [], topReferrers: [], topPages: [] }, 43 + }; 44 + } 45 + 46 + const origin = `https://${domain}/`; 47 + 48 + const [trafficResult, referrersResult, pagesResult] = await Promise.all([ 49 + tinybird.publicationTraffic.query({ 50 + domain: origin, 51 + ...(from ? { date_from: from } : {}), 52 + ...(to ? { date_to: to } : {}), 53 + ...(path ? { path } : {}), 54 + }), 55 + tinybird.publicationTopReferrers.query({ 56 + domain: origin, 57 + ...(from ? { date_from: from } : {}), 58 + ...(to ? { date_to: to } : {}), 59 + ...(path ? { path } : {}), 60 + limit: 10, 61 + }), 62 + tinybird.publicationTopPages.query({ 63 + domain: origin, 64 + ...(from ? { date_from: from } : {}), 65 + ...(to ? { date_to: to } : {}), 66 + limit: 20, 67 + }), 68 + ]); 69 + 70 + return { 71 + result: { 72 + traffic: trafficResult.data, 73 + topReferrers: referrersResult.data, 74 + topPages: pagesResult.data, 75 + }, 76 + }; 77 + }, 78 + });
+79
app/api/rpc/[command]/get_publication_subscribers_timeseries.ts
··· 1 + import { z } from "zod"; 2 + import { makeRoute } from "../lib"; 3 + import type { Env } from "./route"; 4 + import { getIdentityData } from "actions/getIdentityData"; 5 + 6 + export type GetPublicationSubscribersTimeseriesReturnType = Awaited< 7 + ReturnType<(typeof get_publication_subscribers_timeseries)["handler"]> 8 + >; 9 + 10 + export const get_publication_subscribers_timeseries = makeRoute({ 11 + route: "get_publication_subscribers_timeseries", 12 + input: z.object({ 13 + publication_uri: z.string(), 14 + from: z.string().optional(), 15 + to: z.string().optional(), 16 + }), 17 + handler: async ( 18 + { publication_uri, from, to }, 19 + { supabase }: Pick<Env, "supabase">, 20 + ) => { 21 + const identity = await getIdentityData(); 22 + if (!identity?.atp_did || !identity.entitlements?.publication_analytics) { 23 + return { error: "unauthorized" as const }; 24 + } 25 + 26 + // Verify ownership 27 + const { data: publication } = await supabase 28 + .from("publications") 29 + .select("uri, identity_did") 30 + .eq("uri", publication_uri) 31 + .single(); 32 + 33 + if (!publication || publication.identity_did !== identity.atp_did) { 34 + return { error: "not_found" as const }; 35 + } 36 + 37 + let query = supabase 38 + .from("publication_subscriptions") 39 + .select("created_at") 40 + .eq("publication", publication_uri) 41 + .order("created_at", { ascending: true }); 42 + 43 + if (from) { 44 + query = query.gte("created_at", from); 45 + } 46 + if (to) { 47 + query = query.lte("created_at", to); 48 + } 49 + 50 + const { data: subscriptions } = await query; 51 + 52 + // Bucket subscriptions by day and compute cumulative count 53 + const dailyCounts: Record<string, number> = {}; 54 + for (const sub of subscriptions || []) { 55 + const day = sub.created_at.slice(0, 10); 56 + dailyCounts[day] = (dailyCounts[day] || 0) + 1; 57 + } 58 + 59 + const days = Object.keys(dailyCounts).sort(); 60 + let cumulative = 0; 61 + 62 + // If we have a from filter, get the count of subscriptions before that date 63 + if (from) { 64 + const { count } = await supabase 65 + .from("publication_subscriptions") 66 + .select("*", { count: "exact", head: true }) 67 + .eq("publication", publication_uri) 68 + .lt("created_at", from); 69 + cumulative = count || 0; 70 + } 71 + 72 + const timeseries = days.map((day) => { 73 + cumulative += dailyCounts[day]; 74 + return { day, total_subscribers: cumulative }; 75 + }); 76 + 77 + return { result: { timeseries } }; 78 + }, 79 + });
+4
app/api/rpc/[command]/route.ts
··· 17 17 import { get_user_recommendations } from "./get_user_recommendations"; 18 18 import { get_hot_feed } from "./get_hot_feed"; 19 19 import { get_document_interactions } from "./get_document_interactions"; 20 + import { get_publication_analytics } from "./get_publication_analytics"; 21 + import { get_publication_subscribers_timeseries } from "./get_publication_subscribers_timeseries"; 20 22 21 23 let supabase = createClient<Database>( 22 24 process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, ··· 47 49 get_user_recommendations, 48 50 get_hot_feed, 49 51 get_document_interactions, 52 + get_publication_analytics, 53 + get_publication_subscribers_timeseries, 50 54 ]; 51 55 export async function POST( 52 56 req: Request,
+5 -5
app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
··· 123 123 <div className="pubInfo flex text-accent-contrast font-bold justify-between w-full"> 124 124 {props.pubLink} 125 125 </div> 126 - <h2 127 - className={`postTitle text-xl leading-tight pt-0.5 font-bold outline-hidden bg-transparent ${!props.postTitle && "text-tertiary italic"}`} 128 - > 129 - {props.postTitle ? props.postTitle : "Untitled"} 130 - </h2> 126 + {props.postTitle && ( 127 + <h2 className="postTitle text-xl leading-tight pt-0.5 font-bold outline-hidden bg-transparent"> 128 + {props.postTitle} 129 + </h2> 130 + )} 131 131 {props.postDescription ? ( 132 132 <div className="postDescription italic text-secondary outline-hidden bg-transparent pt-1"> 133 133 {props.postDescription}
+243 -48
app/lish/[did]/[publication]/dashboard/PublicationAnalytics.tsx
··· 6 6 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 7 7 import type { DateRange } from "react-day-picker"; 8 8 import { usePublicationData } from "./PublicationSWRProvider"; 9 + import { Combobox, ComboboxResult } from "components/Combobox"; 10 + import { useIsPro } from "src/hooks/useEntitlement"; 11 + import { callRPC } from "app/api/rpc/client"; 12 + import useSWR from "swr"; 9 13 import { 10 - Combobox, 11 - ComboboxResult, 12 - useComboboxState, 13 - } from "components/Combobox"; 14 - import { useIsPro } from "src/hooks/useEntitlement"; 14 + AreaChart, 15 + Area, 16 + XAxis, 17 + YAxis, 18 + CartesianGrid, 19 + Tooltip, 20 + ResponsiveContainer, 21 + } from "recharts"; 22 + 23 + type ReferrerType = { referrer_host: string; pageviews: number }; 24 + 25 + function fillDailyGaps<T extends { day: string }>( 26 + data: T[], 27 + fill: (day: string) => T, 28 + from?: Date, 29 + to?: Date, 30 + ): T[] { 31 + let start = 32 + from || (data.length > 0 ? new Date(data[0].day) : null); 33 + let end = 34 + to || (data.length > 0 ? new Date(data[data.length - 1].day) : null); 35 + if (!start || !end) return data; 36 + 37 + let lookup = new Map(data.map((d) => [d.day, d])); 38 + let result: T[] = []; 39 + let cursor = new Date(start); 40 + cursor.setHours(0, 0, 0, 0); 41 + let endDate = new Date(end); 42 + endDate.setHours(0, 0, 0, 0); 15 43 16 - type referrorType = { iconSrc: string; name: string; viewCount: string }; 17 - let refferors = [ 18 - { iconSrc: "", name: "Bluesky", viewCount: "12k" }, 19 - { iconSrc: "", name: "Reddit", viewCount: "1.2k" }, 20 - { iconSrc: "", name: "X", viewCount: "583" }, 21 - { iconSrc: "", name: "Google", viewCount: "12" }, 22 - ]; 44 + while (cursor <= endDate) { 45 + let key = cursor.toISOString().slice(0, 10); 46 + result.push(lookup.get(key) ?? fill(key)); 47 + cursor.setDate(cursor.getDate() + 1); 48 + } 49 + return result; 50 + } 23 51 24 - export const PublicationAnalytics = () => { 52 + export const PublicationAnalytics = (props: { 53 + showPageBackground: boolean; 54 + }) => { 25 55 let isPro = useIsPro(); 26 56 27 57 let { data: publication } = usePublicationData(); 28 58 let [dateRange, setDateRange] = useState<DateRange>({ from: undefined }); 29 - let [selectedPost, setSelectedPost] = useState<string | undefined>(undefined); 59 + let [selectedPost, setSelectedPost] = useState< 60 + { title: string; path: string } | undefined 61 + >(undefined); 30 62 let [selectedReferror, setSelectedReferror] = useState< 31 - referrorType | undefined 63 + ReferrerType | undefined 32 64 >(undefined); 33 65 66 + let publicationUri = publication?.publication?.uri; 67 + 68 + let { data: analyticsData } = useSWR( 69 + publicationUri 70 + ? [ 71 + "publication-analytics", 72 + publicationUri, 73 + dateRange.from?.toISOString(), 74 + dateRange.to?.toISOString(), 75 + selectedPost?.path, 76 + ] 77 + : null, 78 + async () => { 79 + let res = await callRPC("get_publication_analytics", { 80 + publication_uri: publicationUri!, 81 + ...(dateRange.from ? { from: dateRange.from.toISOString() } : {}), 82 + ...(dateRange.to ? { to: dateRange.to.toISOString() } : {}), 83 + ...(selectedPost ? { path: `/${selectedPost.path}` } : {}), 84 + }); 85 + return res?.result; 86 + }, 87 + ); 88 + 89 + let { data: subscribersData } = useSWR( 90 + publicationUri 91 + ? [ 92 + "publication-subscribers-timeseries", 93 + publicationUri, 94 + dateRange.from?.toISOString(), 95 + dateRange.to?.toISOString(), 96 + ] 97 + : null, 98 + async () => { 99 + let res = await callRPC("get_publication_subscribers_timeseries", { 100 + publication_uri: publicationUri!, 101 + ...(dateRange.from ? { from: dateRange.from.toISOString() } : {}), 102 + ...(dateRange.to ? { to: dateRange.to.toISOString() } : {}), 103 + }); 104 + return res?.result; 105 + }, 106 + ); 107 + 108 + let filledTraffic = useMemo( 109 + () => 110 + fillDailyGaps( 111 + analyticsData?.traffic || [], 112 + (day) => ({ day, pageviews: 0 }), 113 + dateRange.from, 114 + dateRange.to, 115 + ), 116 + [analyticsData?.traffic, dateRange.from, dateRange.to], 117 + ); 118 + 34 119 if (!isPro) 35 120 return ( 36 121 <div className="sm:mx-auto pt-4 s"> ··· 40 125 41 126 return ( 42 127 <div className="analytics flex flex-col gap-6"> 43 - <div className="analyticsSubCount"> 128 + <div 129 + className={`analyticsSubCount rounded-lg border ${ 130 + props.showPageBackground 131 + ? "border-border-light p-2" 132 + : "border-transparent" 133 + }`} 134 + style={{ 135 + backgroundColor: props.showPageBackground 136 + ? "rgba(var(--bg-page), var(--bg-page-alpha))" 137 + : "transparent", 138 + }} 139 + > 44 140 <div className="flex gap-2 justify-between items-center"> 45 141 <h3>Subscribers</h3> 46 142 <DateRangeSelector ··· 49 145 pubStartDate={publication?.publication?.indexed_at} 50 146 /> 51 147 </div> 52 - <div className="aspect-video w-full border border-border grow" /> 148 + <SubscribersChart data={subscribersData?.timeseries || []} /> 53 149 </div> 54 - <div className="analyticsViewCount"> 150 + <div 151 + className={`analyticsViewCount rounded-lg border ${ 152 + props.showPageBackground 153 + ? "border-border-light p-2" 154 + : "border-transparent" 155 + }`} 156 + style={{ 157 + backgroundColor: props.showPageBackground 158 + ? "rgba(var(--bg-page), var(--bg-page-alpha))" 159 + : "transparent", 160 + }} 161 + > 55 162 <div className="flex justify-between items-center gap-2 pb-2 w-full"> 56 163 <div className="flex gap-2 items-center"> 57 164 <h3>Traffic</h3> ··· 63 170 {selectedReferror && ( 64 171 <> 65 172 <ArrowRightTiny className="text-border" /> 66 - <div className="text-tertiary"> {selectedReferror.name}</div> 173 + <div className="text-tertiary"> 174 + {" "} 175 + {selectedReferror.referrer_host} 176 + </div> 67 177 </> 68 178 )} 69 179 </div> ··· 74 184 /> 75 185 </div> 76 186 <div className="flex gap-2"> 77 - <div className="aspect-video w-full border border-border grow" /> 187 + <TrafficChart data={filledTraffic} /> 78 188 <TopReferrors 79 - refferors={refferors} 189 + refferors={analyticsData?.topReferrers || []} 80 190 setSelectedReferror={setSelectedReferror} 81 191 selectedReferror={selectedReferror} 82 192 />{" "} ··· 86 196 ); 87 197 }; 88 198 199 + const SubscribersChart = (props: { 200 + data: { day: string; total_subscribers: number }[]; 201 + }) => { 202 + if (props.data.length === 0) { 203 + return ( 204 + <div className="aspect-video w-full border border-border grow flex items-center justify-center text-tertiary"> 205 + No subscriber data 206 + </div> 207 + ); 208 + } 209 + return ( 210 + <div className="aspect-video w-full grow"> 211 + <ResponsiveContainer width="100%" height="100%"> 212 + <AreaChart data={props.data}> 213 + <CartesianGrid strokeDasharray="3 3" stroke="var(--border)" /> 214 + <XAxis dataKey="day" tick={{ fontSize: 12 }} stroke="var(--tertiary)" /> 215 + <YAxis 216 + tick={{ fontSize: 12 }} 217 + stroke="var(--tertiary)" 218 + allowDecimals={false} 219 + /> 220 + <Tooltip /> 221 + <Area 222 + type="monotone" 223 + dataKey="total_subscribers" 224 + name="Subscribers" 225 + stroke="var(--accent-contrast)" 226 + fill="var(--accent-contrast)" 227 + fillOpacity={0.1} 228 + /> 229 + </AreaChart> 230 + </ResponsiveContainer> 231 + </div> 232 + ); 233 + }; 234 + 235 + const TrafficChart = (props: { 236 + data: { day: string; pageviews: number }[]; 237 + }) => { 238 + if (props.data.length === 0) { 239 + return ( 240 + <div className="aspect-video w-full border border-border grow flex items-center justify-center text-tertiary"> 241 + No traffic data 242 + </div> 243 + ); 244 + } 245 + return ( 246 + <div className="aspect-video w-full grow"> 247 + <ResponsiveContainer width="100%" height="100%"> 248 + <AreaChart data={props.data}> 249 + <CartesianGrid strokeDasharray="3 3" stroke="var(--border)" /> 250 + <XAxis dataKey="day" tick={{ fontSize: 12 }} stroke="var(--tertiary)" /> 251 + <YAxis 252 + tick={{ fontSize: 12 }} 253 + stroke="var(--tertiary)" 254 + allowDecimals={false} 255 + /> 256 + <Tooltip /> 257 + <Area 258 + type="monotone" 259 + dataKey="pageviews" 260 + name="Pageviews" 261 + stroke="var(--accent-contrast)" 262 + fill="var(--accent-contrast)" 263 + fillOpacity={0.1} 264 + /> 265 + </AreaChart> 266 + </ResponsiveContainer> 267 + </div> 268 + ); 269 + }; 270 + 89 271 const PostSelector = (props: { 90 - selectedPost: string | undefined; 91 - setSelectedPost: (s: string | undefined) => void; 272 + selectedPost: { title: string; path: string } | undefined; 273 + setSelectedPost: (s: { title: string; path: string } | undefined) => void; 92 274 }) => { 93 275 let { data } = usePublicationData(); 94 276 let { documents } = data || {}; 277 + 278 + let posts = useMemo( 279 + () => 280 + documents?.map((doc) => ({ 281 + title: doc.record.title, 282 + path: doc.record.path || "", 283 + })), 284 + [documents], 285 + ); 95 286 96 287 let [highlighted, setHighlighted] = useState<string | undefined>(undefined); 97 288 let [searchValue, setSearchValue] = useState<string>(""); 98 289 99 - let open = useComboboxState((s) => s.open); 100 - let posts = documents?.map((doc) => doc.record.title); 101 - let filteredPosts = useMemo( 290 + let postTitles = posts?.map((p) => p.title); 291 + let filteredTitles = useMemo( 102 292 () => 103 - posts && 104 - posts.filter((post) => 105 - post.toLowerCase().includes(searchValue.toLowerCase()), 293 + postTitles && 294 + postTitles.filter((title) => 295 + title.toLowerCase().includes(searchValue.toLowerCase()), 106 296 ), 107 - [searchValue, posts], 297 + [searchValue, postTitles], 108 298 ); 109 299 110 - let filteredPostsWithClear = ["All Posts", ...(filteredPosts || [])]; 300 + let filteredTitlesWithClear = ["All Posts", ...(filteredTitles || [])]; 111 301 112 302 return ( 113 303 <Combobox 114 304 trigger={ 115 305 <button className="text-tertiary"> 116 - {props.selectedPost ?? "All Posts"} 306 + {props.selectedPost?.title ?? "All Posts"} 117 307 </button> 118 308 } 119 - results={filteredPostsWithClear || []} 309 + results={filteredTitlesWithClear || []} 120 310 highlighted={highlighted} 121 311 setHighlighted={setHighlighted} 122 312 onSelect={() => { 123 - props.setSelectedPost(highlighted); 313 + if (highlighted === "All Posts" || !highlighted) { 314 + props.setSelectedPost(undefined); 315 + } else { 316 + let post = posts?.find((p) => p.title === highlighted); 317 + props.setSelectedPost(post); 318 + } 124 319 }} 125 320 sideOffset={2} 126 321 searchValue={searchValue} 127 322 setSearchValue={setSearchValue} 128 323 showSearch 129 324 > 130 - {filteredPostsWithClear.map((post) => { 131 - if (post === "All Posts") 325 + {filteredTitlesWithClear.map((title) => { 326 + if (title === "All Posts") 132 327 return ( 133 328 <> 134 329 <ComboboxResult 135 330 key="all posts" 136 - result={post} 331 + result={title} 137 332 onSelect={() => { 138 333 props.setSelectedPost(undefined); 139 334 }} ··· 142 337 > 143 338 All Posts 144 339 </ComboboxResult> 145 - {filteredPosts && filteredPosts.length !== 0 && ( 340 + {filteredTitles && filteredTitles.length !== 0 && ( 146 341 <hr className="mx-1 border-border-light" /> 147 342 )} 148 343 </> 149 344 ); 150 345 return ( 151 346 <ComboboxResult 152 - key={post} 153 - result={post} 347 + key={title} 348 + result={title} 154 349 onSelect={() => { 350 + let post = posts?.find((p) => p.title === title); 155 351 props.setSelectedPost(post); 156 352 }} 157 353 highlighted={highlighted} 158 354 setHighlighted={setHighlighted} 159 355 > 160 - {post} 356 + {title} 161 357 </ComboboxResult> 162 358 ); 163 359 })} ··· 250 446 }; 251 447 252 448 const TopReferrors = (props: { 253 - refferors: referrorType[]; 254 - setSelectedReferror: (ref: referrorType) => void; 255 - selectedReferror: referrorType | undefined; 449 + refferors: ReferrerType[]; 450 + setSelectedReferror: (ref: ReferrerType) => void; 451 + selectedReferror: ReferrerType | undefined; 256 452 }) => { 257 453 return ( 258 454 <div className="topReferrors flex flex-col gap-0.5 w-full sm:w-xs"> ··· 261 457 return ( 262 458 <> 263 459 <button 264 - key={ref.name} 460 + key={ref.referrer_host} 265 461 className={`w-full flex justify-between gap-4 px-1 items-center text-right rounded-md ${selected ? "text-accent-contrast bg-[var(--accent-light)]" : ""}`} 266 462 onClick={() => { 267 463 props.setSelectedReferror(ref); 268 464 }} 269 465 > 270 466 <div className="flex gap-2 items-center grow"> 271 - <img src={ref.iconSrc} className="h-4 w-4" aria-hidden /> 272 - {ref.name} 467 + {ref.referrer_host} 273 468 </div> 274 - {ref.viewCount} 469 + {ref.pageviews.toLocaleString()} 275 470 </button> 276 471 <hr className="border-border-light last:hidden" /> 277 472 </>
+5 -1
app/lish/[did]/[publication]/dashboard/PublicationDashboard.tsx
··· 74 74 controls: null, 75 75 }, 76 76 Analytics: { 77 - content: <PublicationAnalytics />, 77 + content: ( 78 + <PublicationAnalytics 79 + showPageBackground={!!record.theme?.showPageBackground} 80 + /> 81 + ), 78 82 controls: null, 79 83 }, 80 84 }}
+5 -3
components/PostListing.tsx
··· 133 133 </div> 134 134 )} 135 135 <div className="postListingInfo px-3 py-2"> 136 - <h3 className="postListingTitle text-primary line-clamp-2 sm:text-lg text-base"> 137 - {postRecord.title} 138 - </h3> 136 + {postRecord.title && ( 137 + <h3 className="postListingTitle text-primary line-clamp-2 sm:text-lg text-base"> 138 + {postRecord.title} 139 + </h3> 140 + )} 139 141 140 142 <p className="postListingDescription text-secondary line-clamp-3 sm:text-base text-sm"> 141 143 {postRecord.description}
+235
lib/tinybird.ts
··· 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 + 11 + import { 12 + defineDatasource, 13 + defineEndpoint, 14 + Tinybird, 15 + node, 16 + t, 17 + p, 18 + engine, 19 + type InferRow, 20 + type InferParams, 21 + type InferOutputRow, 22 + } from "@tinybirdco/sdk"; 23 + 24 + // ============================================================================ 25 + // Datasources 26 + // ============================================================================ 27 + 28 + /** 29 + * Vercel Web Analytics drain events. 30 + * Column names match the Vercel drain JSON keys exactly. 31 + * `timestamp` is stored as UInt64 (Unix millis) as sent by Vercel. 32 + */ 33 + export const analyticsEvents = defineDatasource("analytics_events", { 34 + description: "Vercel Web Analytics drain events", 35 + schema: { 36 + timestamp: t.uint64(), 37 + eventType: t.string().lowCardinality(), 38 + eventName: t.string().default(""), 39 + eventData: t.string().default(""), 40 + sessionId: t.uint64(), 41 + deviceId: t.uint64(), 42 + origin: t.string(), 43 + path: t.string(), 44 + referrer: t.string().default(""), 45 + queryParams: t.string().default(""), 46 + route: t.string().default(""), 47 + country: t.string().lowCardinality().default(""), 48 + region: t.string().default(""), 49 + city: t.string().default(""), 50 + osName: t.string().lowCardinality().default(""), 51 + osVersion: t.string().default(""), 52 + clientName: t.string().lowCardinality().default(""), 53 + clientType: t.string().lowCardinality().default(""), 54 + clientVersion: t.string().default(""), 55 + deviceType: t.string().lowCardinality().default(""), 56 + deviceBrand: t.string().default(""), 57 + deviceModel: t.string().default(""), 58 + browserEngine: t.string().default(""), 59 + browserEngineVersion: t.string().default(""), 60 + sdkVersion: t.string().default(""), 61 + sdkName: t.string().default(""), 62 + sdkVersionFull: t.string().default(""), 63 + vercelEnvironment: t.string().lowCardinality().default(""), 64 + vercelUrl: t.string().default(""), 65 + flags: t.string().default(""), 66 + deployment: t.string().default(""), 67 + schema: t.string().default(""), 68 + projectId: t.string().default(""), 69 + ownerId: t.string().default(""), 70 + dataSourceName: t.string().default(""), 71 + }, 72 + engine: engine.mergeTree({ 73 + sortingKey: ["origin", "timestamp"], 74 + partitionKey: "toYYYYMM(fromUnixTimestamp64Milli(timestamp))", 75 + }), 76 + }); 77 + 78 + export type AnalyticsEventsRow = InferRow<typeof analyticsEvents>; 79 + 80 + // ============================================================================ 81 + // Endpoints 82 + // ============================================================================ 83 + 84 + /** 85 + * publication_traffic – daily pageview time series for a publication domain. 86 + */ 87 + export const publicationTraffic = defineEndpoint("publication_traffic", { 88 + description: "Daily pageview time series for a publication domain", 89 + params: { 90 + domain: p.string(), 91 + date_from: p.string().optional(), 92 + date_to: p.string().optional(), 93 + path: p.string().optional(), 94 + }, 95 + nodes: [ 96 + node({ 97 + name: "endpoint", 98 + sql: ` 99 + SELECT 100 + toDate(fromUnixTimestamp64Milli(timestamp)) AS day, 101 + count() AS pageviews 102 + FROM analytics_events 103 + WHERE eventType = 'pageview' 104 + AND origin = {{String(domain)}} 105 + {% if defined(date_from) %} 106 + AND fromUnixTimestamp64Milli(timestamp) >= parseDateTimeBestEffort({{String(date_from)}}) 107 + {% end %} 108 + {% if defined(date_to) %} 109 + AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}}) 110 + {% end %} 111 + {% if defined(path) %} 112 + AND path = {{String(path)}} 113 + {% end %} 114 + GROUP BY day 115 + ORDER BY day ASC 116 + `, 117 + }), 118 + ], 119 + output: { 120 + day: t.date(), 121 + pageviews: t.uint64(), 122 + }, 123 + }); 124 + 125 + export type PublicationTrafficParams = InferParams<typeof publicationTraffic>; 126 + export type PublicationTrafficOutput = InferOutputRow<typeof publicationTraffic>; 127 + 128 + /** 129 + * publication_top_referrers – top referring domains for a publication. 130 + */ 131 + export const publicationTopReferrers = defineEndpoint( 132 + "publication_top_referrers", 133 + { 134 + description: "Top referrers for a publication domain", 135 + params: { 136 + domain: p.string(), 137 + date_from: p.string().optional(), 138 + date_to: p.string().optional(), 139 + path: p.string().optional(), 140 + limit: p.int32().optional(10), 141 + }, 142 + nodes: [ 143 + node({ 144 + name: "endpoint", 145 + sql: ` 146 + SELECT 147 + domain(referrer) AS referrer_host, 148 + count() AS pageviews 149 + FROM analytics_events 150 + WHERE eventType = 'pageview' 151 + AND origin = {{String(domain)}} 152 + AND referrer != '' 153 + AND domain(referrer) != domain({{String(domain)}}) 154 + {% if defined(date_from) %} 155 + AND fromUnixTimestamp64Milli(timestamp) >= parseDateTimeBestEffort({{String(date_from)}}) 156 + {% end %} 157 + {% if defined(date_to) %} 158 + AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}}) 159 + {% end %} 160 + {% if defined(path) %} 161 + AND path = {{String(path)}} 162 + {% end %} 163 + GROUP BY referrer_host 164 + ORDER BY pageviews DESC 165 + LIMIT {{Int32(limit, 10)}} 166 + `, 167 + }), 168 + ], 169 + output: { 170 + referrer_host: t.string(), 171 + pageviews: t.uint64(), 172 + }, 173 + }, 174 + ); 175 + 176 + export type PublicationTopReferrersParams = InferParams< 177 + typeof publicationTopReferrers 178 + >; 179 + export type PublicationTopReferrersOutput = InferOutputRow< 180 + typeof publicationTopReferrers 181 + >; 182 + 183 + /** 184 + * publication_top_pages – top pages by pageviews for a publication. 185 + */ 186 + export const publicationTopPages = defineEndpoint("publication_top_pages", { 187 + description: "Top pages for a publication domain", 188 + params: { 189 + domain: p.string(), 190 + date_from: p.string().optional(), 191 + date_to: p.string().optional(), 192 + limit: p.int32().optional(10), 193 + }, 194 + nodes: [ 195 + node({ 196 + name: "endpoint", 197 + sql: ` 198 + SELECT 199 + path, 200 + count() AS pageviews 201 + FROM analytics_events 202 + WHERE eventType = 'pageview' 203 + AND origin = {{String(domain)}} 204 + {% if defined(date_from) %} 205 + AND fromUnixTimestamp64Milli(timestamp) >= parseDateTimeBestEffort({{String(date_from)}}) 206 + {% end %} 207 + {% if defined(date_to) %} 208 + AND fromUnixTimestamp64Milli(timestamp) <= parseDateTimeBestEffort({{String(date_to)}}) 209 + {% end %} 210 + GROUP BY path 211 + ORDER BY pageviews DESC 212 + LIMIT {{Int32(limit, 10)}} 213 + `, 214 + }), 215 + ], 216 + output: { 217 + path: t.string(), 218 + pageviews: t.uint64(), 219 + }, 220 + }); 221 + 222 + export type PublicationTopPagesParams = InferParams<typeof publicationTopPages>; 223 + export type PublicationTopPagesOutput = InferOutputRow< 224 + typeof publicationTopPages 225 + >; 226 + 227 + // ============================================================================ 228 + // Client 229 + // ============================================================================ 230 + 231 + export const tinybird = new Tinybird({ 232 + datasources: { analyticsEvents }, 233 + pipes: { publicationTraffic, publicationTopReferrers, publicationTopPages }, 234 + devMode: false, 235 + });
+905 -3
package-lock.json
··· 32 32 "@rocicorp/undo": "^0.2.1", 33 33 "@supabase/ssr": "^0.3.0", 34 34 "@supabase/supabase-js": "^2.43.2", 35 + "@tinybirdco/sdk": "^0.0.55", 35 36 "@tiptap/core": "^2.11.5", 36 37 "@types/mdx": "^2.0.13", 37 38 "@vercel/analytics": "^1.5.0", ··· 66 67 "react-day-picker": "^9.3.0", 67 68 "react-dom": "19.2.1", 68 69 "react-use-measure": "^2.1.1", 70 + "recharts": "^3.7.0", 69 71 "redlock": "^5.0.0-beta.2", 70 72 "rehype-parse": "^9.0.0", 71 73 "rehype-remark": "^10.0.0", ··· 945 947 "os": [ 946 948 "linux" 947 949 ] 950 + }, 951 + "node_modules/@clack/core": { 952 + "version": "1.0.1", 953 + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.1.tgz", 954 + "integrity": "sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==", 955 + "license": "MIT", 956 + "dependencies": { 957 + "picocolors": "^1.0.0", 958 + "sisteransi": "^1.0.5" 959 + } 960 + }, 961 + "node_modules/@clack/prompts": { 962 + "version": "1.0.1", 963 + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.1.tgz", 964 + "integrity": "sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==", 965 + "license": "MIT", 966 + "dependencies": { 967 + "@clack/core": "1.0.1", 968 + "picocolors": "^1.0.0", 969 + "sisteransi": "^1.0.5" 970 + } 948 971 }, 949 972 "node_modules/@cloudflare/kv-asset-handler": { 950 973 "version": "0.3.2", ··· 7120 7143 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" 7121 7144 } 7122 7145 }, 7146 + "node_modules/@reduxjs/toolkit": { 7147 + "version": "2.11.2", 7148 + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", 7149 + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", 7150 + "license": "MIT", 7151 + "dependencies": { 7152 + "@standard-schema/spec": "^1.0.0", 7153 + "@standard-schema/utils": "^0.3.0", 7154 + "immer": "^11.0.0", 7155 + "redux": "^5.0.1", 7156 + "redux-thunk": "^3.1.0", 7157 + "reselect": "^5.1.0" 7158 + }, 7159 + "peerDependencies": { 7160 + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", 7161 + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" 7162 + }, 7163 + "peerDependenciesMeta": { 7164 + "react": { 7165 + "optional": true 7166 + }, 7167 + "react-redux": { 7168 + "optional": true 7169 + } 7170 + } 7171 + }, 7172 + "node_modules/@reduxjs/toolkit/node_modules/immer": { 7173 + "version": "11.1.4", 7174 + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", 7175 + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", 7176 + "license": "MIT", 7177 + "funding": { 7178 + "type": "opencollective", 7179 + "url": "https://opencollective.com/immer" 7180 + } 7181 + }, 7123 7182 "node_modules/@remirror/core-constants": { 7124 7183 "version": "3.0.0", 7125 7184 "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", ··· 7225 7284 "version": "10.0.2", 7226 7285 "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", 7227 7286 "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==" 7287 + }, 7288 + "node_modules/@standard-schema/spec": { 7289 + "version": "1.1.0", 7290 + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", 7291 + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", 7292 + "license": "MIT" 7293 + }, 7294 + "node_modules/@standard-schema/utils": { 7295 + "version": "0.3.0", 7296 + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", 7297 + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", 7298 + "license": "MIT" 7228 7299 }, 7229 7300 "node_modules/@supabase/auth-js": { 7230 7301 "version": "2.64.2", ··· 7617 7688 "tailwindcss": "4.1.13" 7618 7689 } 7619 7690 }, 7691 + "node_modules/@tinybirdco/sdk": { 7692 + "version": "0.0.55", 7693 + "resolved": "https://registry.npmjs.org/@tinybirdco/sdk/-/sdk-0.0.55.tgz", 7694 + "integrity": "sha512-LzOocxjdGy1nXD+03zfY3C4ZQSOnH1CbarMajUnwOwi4OwDeJIEA3nohyPxWAxgXXRgH7kfhM/tokavhP+DH0A==", 7695 + "license": "MIT", 7696 + "dependencies": { 7697 + "@clack/prompts": "^1.0.0", 7698 + "chokidar": "^4.0.0", 7699 + "commander": "^12.0.0", 7700 + "dotenv": "^16.0.0", 7701 + "esbuild": "^0.24.0", 7702 + "picocolors": "^1.1.1", 7703 + "zod": "^3.25.0" 7704 + }, 7705 + "bin": { 7706 + "tinybird": "bin/tinybird.js" 7707 + }, 7708 + "engines": { 7709 + "node": ">=20.0.0" 7710 + } 7711 + }, 7712 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/aix-ppc64": { 7713 + "version": "0.24.2", 7714 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", 7715 + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", 7716 + "cpu": [ 7717 + "ppc64" 7718 + ], 7719 + "license": "MIT", 7720 + "optional": true, 7721 + "os": [ 7722 + "aix" 7723 + ], 7724 + "engines": { 7725 + "node": ">=18" 7726 + } 7727 + }, 7728 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/android-arm": { 7729 + "version": "0.24.2", 7730 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", 7731 + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", 7732 + "cpu": [ 7733 + "arm" 7734 + ], 7735 + "license": "MIT", 7736 + "optional": true, 7737 + "os": [ 7738 + "android" 7739 + ], 7740 + "engines": { 7741 + "node": ">=18" 7742 + } 7743 + }, 7744 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/android-arm64": { 7745 + "version": "0.24.2", 7746 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", 7747 + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", 7748 + "cpu": [ 7749 + "arm64" 7750 + ], 7751 + "license": "MIT", 7752 + "optional": true, 7753 + "os": [ 7754 + "android" 7755 + ], 7756 + "engines": { 7757 + "node": ">=18" 7758 + } 7759 + }, 7760 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/android-x64": { 7761 + "version": "0.24.2", 7762 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", 7763 + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", 7764 + "cpu": [ 7765 + "x64" 7766 + ], 7767 + "license": "MIT", 7768 + "optional": true, 7769 + "os": [ 7770 + "android" 7771 + ], 7772 + "engines": { 7773 + "node": ">=18" 7774 + } 7775 + }, 7776 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/darwin-arm64": { 7777 + "version": "0.24.2", 7778 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", 7779 + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", 7780 + "cpu": [ 7781 + "arm64" 7782 + ], 7783 + "license": "MIT", 7784 + "optional": true, 7785 + "os": [ 7786 + "darwin" 7787 + ], 7788 + "engines": { 7789 + "node": ">=18" 7790 + } 7791 + }, 7792 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/darwin-x64": { 7793 + "version": "0.24.2", 7794 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", 7795 + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", 7796 + "cpu": [ 7797 + "x64" 7798 + ], 7799 + "license": "MIT", 7800 + "optional": true, 7801 + "os": [ 7802 + "darwin" 7803 + ], 7804 + "engines": { 7805 + "node": ">=18" 7806 + } 7807 + }, 7808 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/freebsd-arm64": { 7809 + "version": "0.24.2", 7810 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", 7811 + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", 7812 + "cpu": [ 7813 + "arm64" 7814 + ], 7815 + "license": "MIT", 7816 + "optional": true, 7817 + "os": [ 7818 + "freebsd" 7819 + ], 7820 + "engines": { 7821 + "node": ">=18" 7822 + } 7823 + }, 7824 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/freebsd-x64": { 7825 + "version": "0.24.2", 7826 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", 7827 + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", 7828 + "cpu": [ 7829 + "x64" 7830 + ], 7831 + "license": "MIT", 7832 + "optional": true, 7833 + "os": [ 7834 + "freebsd" 7835 + ], 7836 + "engines": { 7837 + "node": ">=18" 7838 + } 7839 + }, 7840 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-arm": { 7841 + "version": "0.24.2", 7842 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", 7843 + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", 7844 + "cpu": [ 7845 + "arm" 7846 + ], 7847 + "license": "MIT", 7848 + "optional": true, 7849 + "os": [ 7850 + "linux" 7851 + ], 7852 + "engines": { 7853 + "node": ">=18" 7854 + } 7855 + }, 7856 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-arm64": { 7857 + "version": "0.24.2", 7858 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", 7859 + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", 7860 + "cpu": [ 7861 + "arm64" 7862 + ], 7863 + "license": "MIT", 7864 + "optional": true, 7865 + "os": [ 7866 + "linux" 7867 + ], 7868 + "engines": { 7869 + "node": ">=18" 7870 + } 7871 + }, 7872 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-ia32": { 7873 + "version": "0.24.2", 7874 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", 7875 + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", 7876 + "cpu": [ 7877 + "ia32" 7878 + ], 7879 + "license": "MIT", 7880 + "optional": true, 7881 + "os": [ 7882 + "linux" 7883 + ], 7884 + "engines": { 7885 + "node": ">=18" 7886 + } 7887 + }, 7888 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-loong64": { 7889 + "version": "0.24.2", 7890 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", 7891 + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", 7892 + "cpu": [ 7893 + "loong64" 7894 + ], 7895 + "license": "MIT", 7896 + "optional": true, 7897 + "os": [ 7898 + "linux" 7899 + ], 7900 + "engines": { 7901 + "node": ">=18" 7902 + } 7903 + }, 7904 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-mips64el": { 7905 + "version": "0.24.2", 7906 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", 7907 + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", 7908 + "cpu": [ 7909 + "mips64el" 7910 + ], 7911 + "license": "MIT", 7912 + "optional": true, 7913 + "os": [ 7914 + "linux" 7915 + ], 7916 + "engines": { 7917 + "node": ">=18" 7918 + } 7919 + }, 7920 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-ppc64": { 7921 + "version": "0.24.2", 7922 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", 7923 + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", 7924 + "cpu": [ 7925 + "ppc64" 7926 + ], 7927 + "license": "MIT", 7928 + "optional": true, 7929 + "os": [ 7930 + "linux" 7931 + ], 7932 + "engines": { 7933 + "node": ">=18" 7934 + } 7935 + }, 7936 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-riscv64": { 7937 + "version": "0.24.2", 7938 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", 7939 + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", 7940 + "cpu": [ 7941 + "riscv64" 7942 + ], 7943 + "license": "MIT", 7944 + "optional": true, 7945 + "os": [ 7946 + "linux" 7947 + ], 7948 + "engines": { 7949 + "node": ">=18" 7950 + } 7951 + }, 7952 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-s390x": { 7953 + "version": "0.24.2", 7954 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", 7955 + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", 7956 + "cpu": [ 7957 + "s390x" 7958 + ], 7959 + "license": "MIT", 7960 + "optional": true, 7961 + "os": [ 7962 + "linux" 7963 + ], 7964 + "engines": { 7965 + "node": ">=18" 7966 + } 7967 + }, 7968 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/linux-x64": { 7969 + "version": "0.24.2", 7970 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", 7971 + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", 7972 + "cpu": [ 7973 + "x64" 7974 + ], 7975 + "license": "MIT", 7976 + "optional": true, 7977 + "os": [ 7978 + "linux" 7979 + ], 7980 + "engines": { 7981 + "node": ">=18" 7982 + } 7983 + }, 7984 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/netbsd-arm64": { 7985 + "version": "0.24.2", 7986 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", 7987 + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", 7988 + "cpu": [ 7989 + "arm64" 7990 + ], 7991 + "license": "MIT", 7992 + "optional": true, 7993 + "os": [ 7994 + "netbsd" 7995 + ], 7996 + "engines": { 7997 + "node": ">=18" 7998 + } 7999 + }, 8000 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/netbsd-x64": { 8001 + "version": "0.24.2", 8002 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", 8003 + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", 8004 + "cpu": [ 8005 + "x64" 8006 + ], 8007 + "license": "MIT", 8008 + "optional": true, 8009 + "os": [ 8010 + "netbsd" 8011 + ], 8012 + "engines": { 8013 + "node": ">=18" 8014 + } 8015 + }, 8016 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/openbsd-arm64": { 8017 + "version": "0.24.2", 8018 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", 8019 + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", 8020 + "cpu": [ 8021 + "arm64" 8022 + ], 8023 + "license": "MIT", 8024 + "optional": true, 8025 + "os": [ 8026 + "openbsd" 8027 + ], 8028 + "engines": { 8029 + "node": ">=18" 8030 + } 8031 + }, 8032 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/openbsd-x64": { 8033 + "version": "0.24.2", 8034 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", 8035 + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", 8036 + "cpu": [ 8037 + "x64" 8038 + ], 8039 + "license": "MIT", 8040 + "optional": true, 8041 + "os": [ 8042 + "openbsd" 8043 + ], 8044 + "engines": { 8045 + "node": ">=18" 8046 + } 8047 + }, 8048 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/sunos-x64": { 8049 + "version": "0.24.2", 8050 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", 8051 + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", 8052 + "cpu": [ 8053 + "x64" 8054 + ], 8055 + "license": "MIT", 8056 + "optional": true, 8057 + "os": [ 8058 + "sunos" 8059 + ], 8060 + "engines": { 8061 + "node": ">=18" 8062 + } 8063 + }, 8064 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/win32-arm64": { 8065 + "version": "0.24.2", 8066 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", 8067 + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", 8068 + "cpu": [ 8069 + "arm64" 8070 + ], 8071 + "license": "MIT", 8072 + "optional": true, 8073 + "os": [ 8074 + "win32" 8075 + ], 8076 + "engines": { 8077 + "node": ">=18" 8078 + } 8079 + }, 8080 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/win32-ia32": { 8081 + "version": "0.24.2", 8082 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", 8083 + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", 8084 + "cpu": [ 8085 + "ia32" 8086 + ], 8087 + "license": "MIT", 8088 + "optional": true, 8089 + "os": [ 8090 + "win32" 8091 + ], 8092 + "engines": { 8093 + "node": ">=18" 8094 + } 8095 + }, 8096 + "node_modules/@tinybirdco/sdk/node_modules/@esbuild/win32-x64": { 8097 + "version": "0.24.2", 8098 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", 8099 + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", 8100 + "cpu": [ 8101 + "x64" 8102 + ], 8103 + "license": "MIT", 8104 + "optional": true, 8105 + "os": [ 8106 + "win32" 8107 + ], 8108 + "engines": { 8109 + "node": ">=18" 8110 + } 8111 + }, 8112 + "node_modules/@tinybirdco/sdk/node_modules/chokidar": { 8113 + "version": "4.0.3", 8114 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 8115 + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", 8116 + "license": "MIT", 8117 + "dependencies": { 8118 + "readdirp": "^4.0.1" 8119 + }, 8120 + "engines": { 8121 + "node": ">= 14.16.0" 8122 + }, 8123 + "funding": { 8124 + "url": "https://paulmillr.com/funding/" 8125 + } 8126 + }, 8127 + "node_modules/@tinybirdco/sdk/node_modules/commander": { 8128 + "version": "12.1.0", 8129 + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 8130 + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 8131 + "license": "MIT", 8132 + "engines": { 8133 + "node": ">=18" 8134 + } 8135 + }, 8136 + "node_modules/@tinybirdco/sdk/node_modules/esbuild": { 8137 + "version": "0.24.2", 8138 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", 8139 + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", 8140 + "hasInstallScript": true, 8141 + "license": "MIT", 8142 + "bin": { 8143 + "esbuild": "bin/esbuild" 8144 + }, 8145 + "engines": { 8146 + "node": ">=18" 8147 + }, 8148 + "optionalDependencies": { 8149 + "@esbuild/aix-ppc64": "0.24.2", 8150 + "@esbuild/android-arm": "0.24.2", 8151 + "@esbuild/android-arm64": "0.24.2", 8152 + "@esbuild/android-x64": "0.24.2", 8153 + "@esbuild/darwin-arm64": "0.24.2", 8154 + "@esbuild/darwin-x64": "0.24.2", 8155 + "@esbuild/freebsd-arm64": "0.24.2", 8156 + "@esbuild/freebsd-x64": "0.24.2", 8157 + "@esbuild/linux-arm": "0.24.2", 8158 + "@esbuild/linux-arm64": "0.24.2", 8159 + "@esbuild/linux-ia32": "0.24.2", 8160 + "@esbuild/linux-loong64": "0.24.2", 8161 + "@esbuild/linux-mips64el": "0.24.2", 8162 + "@esbuild/linux-ppc64": "0.24.2", 8163 + "@esbuild/linux-riscv64": "0.24.2", 8164 + "@esbuild/linux-s390x": "0.24.2", 8165 + "@esbuild/linux-x64": "0.24.2", 8166 + "@esbuild/netbsd-arm64": "0.24.2", 8167 + "@esbuild/netbsd-x64": "0.24.2", 8168 + "@esbuild/openbsd-arm64": "0.24.2", 8169 + "@esbuild/openbsd-x64": "0.24.2", 8170 + "@esbuild/sunos-x64": "0.24.2", 8171 + "@esbuild/win32-arm64": "0.24.2", 8172 + "@esbuild/win32-ia32": "0.24.2", 8173 + "@esbuild/win32-x64": "0.24.2" 8174 + } 8175 + }, 8176 + "node_modules/@tinybirdco/sdk/node_modules/readdirp": { 8177 + "version": "4.1.2", 8178 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", 8179 + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", 8180 + "license": "MIT", 8181 + "engines": { 8182 + "node": ">= 14.18.0" 8183 + }, 8184 + "funding": { 8185 + "type": "individual", 8186 + "url": "https://paulmillr.com/funding/" 8187 + } 8188 + }, 7620 8189 "node_modules/@tiptap/core": { 7621 8190 "version": "2.11.5", 7622 8191 "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz", ··· 7731 8300 "@types/node": "*" 7732 8301 } 7733 8302 }, 8303 + "node_modules/@types/d3-array": { 8304 + "version": "3.2.2", 8305 + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", 8306 + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", 8307 + "license": "MIT" 8308 + }, 8309 + "node_modules/@types/d3-color": { 8310 + "version": "3.1.3", 8311 + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", 8312 + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", 8313 + "license": "MIT" 8314 + }, 8315 + "node_modules/@types/d3-ease": { 8316 + "version": "3.0.2", 8317 + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", 8318 + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", 8319 + "license": "MIT" 8320 + }, 8321 + "node_modules/@types/d3-interpolate": { 8322 + "version": "3.0.4", 8323 + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", 8324 + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", 8325 + "license": "MIT", 8326 + "dependencies": { 8327 + "@types/d3-color": "*" 8328 + } 8329 + }, 8330 + "node_modules/@types/d3-path": { 8331 + "version": "3.1.1", 8332 + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", 8333 + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", 8334 + "license": "MIT" 8335 + }, 8336 + "node_modules/@types/d3-scale": { 8337 + "version": "4.0.9", 8338 + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", 8339 + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", 8340 + "license": "MIT", 8341 + "dependencies": { 8342 + "@types/d3-time": "*" 8343 + } 8344 + }, 8345 + "node_modules/@types/d3-shape": { 8346 + "version": "3.1.8", 8347 + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", 8348 + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", 8349 + "license": "MIT", 8350 + "dependencies": { 8351 + "@types/d3-path": "*" 8352 + } 8353 + }, 8354 + "node_modules/@types/d3-time": { 8355 + "version": "3.0.4", 8356 + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", 8357 + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", 8358 + "license": "MIT" 8359 + }, 8360 + "node_modules/@types/d3-timer": { 8361 + "version": "3.0.2", 8362 + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", 8363 + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", 8364 + "license": "MIT" 8365 + }, 7734 8366 "node_modules/@types/debug": { 7735 8367 "version": "4.1.12", 7736 8368 "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", ··· 7927 8559 "version": "3.0.3", 7928 8560 "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", 7929 8561 "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", 8562 + "license": "MIT" 8563 + }, 8564 + "node_modules/@types/use-sync-external-store": { 8565 + "version": "0.0.6", 8566 + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", 8567 + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", 7930 8568 "license": "MIT" 7931 8569 }, 7932 8570 "node_modules/@types/uuid": { ··· 9429 10067 "node": ">=0.12" 9430 10068 } 9431 10069 }, 10070 + "node_modules/d3-array": { 10071 + "version": "3.2.4", 10072 + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", 10073 + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", 10074 + "license": "ISC", 10075 + "dependencies": { 10076 + "internmap": "1 - 2" 10077 + }, 10078 + "engines": { 10079 + "node": ">=12" 10080 + } 10081 + }, 10082 + "node_modules/d3-color": { 10083 + "version": "3.1.0", 10084 + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", 10085 + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", 10086 + "license": "ISC", 10087 + "engines": { 10088 + "node": ">=12" 10089 + } 10090 + }, 10091 + "node_modules/d3-ease": { 10092 + "version": "3.0.1", 10093 + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", 10094 + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", 10095 + "license": "BSD-3-Clause", 10096 + "engines": { 10097 + "node": ">=12" 10098 + } 10099 + }, 10100 + "node_modules/d3-format": { 10101 + "version": "3.1.2", 10102 + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", 10103 + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", 10104 + "license": "ISC", 10105 + "engines": { 10106 + "node": ">=12" 10107 + } 10108 + }, 10109 + "node_modules/d3-interpolate": { 10110 + "version": "3.0.1", 10111 + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", 10112 + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", 10113 + "license": "ISC", 10114 + "dependencies": { 10115 + "d3-color": "1 - 3" 10116 + }, 10117 + "engines": { 10118 + "node": ">=12" 10119 + } 10120 + }, 10121 + "node_modules/d3-path": { 10122 + "version": "3.1.0", 10123 + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", 10124 + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", 10125 + "license": "ISC", 10126 + "engines": { 10127 + "node": ">=12" 10128 + } 10129 + }, 10130 + "node_modules/d3-scale": { 10131 + "version": "4.0.2", 10132 + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", 10133 + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", 10134 + "license": "ISC", 10135 + "dependencies": { 10136 + "d3-array": "2.10.0 - 3", 10137 + "d3-format": "1 - 3", 10138 + "d3-interpolate": "1.2.0 - 3", 10139 + "d3-time": "2.1.1 - 3", 10140 + "d3-time-format": "2 - 4" 10141 + }, 10142 + "engines": { 10143 + "node": ">=12" 10144 + } 10145 + }, 10146 + "node_modules/d3-shape": { 10147 + "version": "3.2.0", 10148 + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", 10149 + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", 10150 + "license": "ISC", 10151 + "dependencies": { 10152 + "d3-path": "^3.1.0" 10153 + }, 10154 + "engines": { 10155 + "node": ">=12" 10156 + } 10157 + }, 10158 + "node_modules/d3-time": { 10159 + "version": "3.1.0", 10160 + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", 10161 + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", 10162 + "license": "ISC", 10163 + "dependencies": { 10164 + "d3-array": "2 - 3" 10165 + }, 10166 + "engines": { 10167 + "node": ">=12" 10168 + } 10169 + }, 10170 + "node_modules/d3-time-format": { 10171 + "version": "4.1.0", 10172 + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", 10173 + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", 10174 + "license": "ISC", 10175 + "dependencies": { 10176 + "d3-time": "1 - 3" 10177 + }, 10178 + "engines": { 10179 + "node": ">=12" 10180 + } 10181 + }, 10182 + "node_modules/d3-timer": { 10183 + "version": "3.0.1", 10184 + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", 10185 + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", 10186 + "license": "ISC", 10187 + "engines": { 10188 + "node": ">=12" 10189 + } 10190 + }, 9432 10191 "node_modules/damerau-levenshtein": { 9433 10192 "version": "1.0.8", 9434 10193 "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", ··· 9540 10299 "version": "10.5.0", 9541 10300 "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", 9542 10301 "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", 10302 + "license": "MIT" 10303 + }, 10304 + "node_modules/decimal.js-light": { 10305 + "version": "2.5.1", 10306 + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", 10307 + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", 9543 10308 "license": "MIT" 9544 10309 }, 9545 10310 "node_modules/decode-named-character-reference": { ··· 9687 10452 }, 9688 10453 "engines": { 9689 10454 "node": ">=0.10.0" 10455 + } 10456 + }, 10457 + "node_modules/dotenv": { 10458 + "version": "16.6.1", 10459 + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", 10460 + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", 10461 + "license": "BSD-2-Clause", 10462 + "engines": { 10463 + "node": ">=12" 10464 + }, 10465 + "funding": { 10466 + "url": "https://dotenvx.com" 9690 10467 } 9691 10468 }, 9692 10469 "node_modules/dreamopt": { ··· 10501 11278 "funding": { 10502 11279 "url": "https://github.com/sponsors/ljharb" 10503 11280 } 11281 + }, 11282 + "node_modules/es-toolkit": { 11283 + "version": "1.45.0", 11284 + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.0.tgz", 11285 + "integrity": "sha512-RArCX+Zea16+R1jg4mH223Z8p/ivbJjIkU3oC6ld2bdUfmDxiCkFYSi9zLOR2anucWJUeH4Djnzgd0im0nD3dw==", 11286 + "license": "MIT", 11287 + "workspaces": [ 11288 + "docs", 11289 + "benchmarks" 11290 + ] 10504 11291 }, 10505 11292 "node_modules/es5-ext": { 10506 11293 "version": "0.10.64", ··· 12705 13492 "node": ">= 0.4" 12706 13493 } 12707 13494 }, 13495 + "node_modules/internmap": { 13496 + "version": "2.0.3", 13497 + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", 13498 + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", 13499 + "license": "ISC", 13500 + "engines": { 13501 + "node": ">=12" 13502 + } 13503 + }, 12708 13504 "node_modules/intl-messageformat": { 12709 13505 "version": "10.7.16", 12710 13506 "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", ··· 16579 17375 "version": "16.13.1", 16580 17376 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 16581 17377 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 16582 - "dev": true, 16583 17378 "license": "MIT" 17379 + }, 17380 + "node_modules/react-redux": { 17381 + "version": "9.2.0", 17382 + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", 17383 + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", 17384 + "license": "MIT", 17385 + "dependencies": { 17386 + "@types/use-sync-external-store": "^0.0.6", 17387 + "use-sync-external-store": "^1.4.0" 17388 + }, 17389 + "peerDependencies": { 17390 + "@types/react": "^18.2.25 || ^19", 17391 + "react": "^18.0 || ^19", 17392 + "redux": "^5.0.0" 17393 + }, 17394 + "peerDependenciesMeta": { 17395 + "@types/react": { 17396 + "optional": true 17397 + }, 17398 + "redux": { 17399 + "optional": true 17400 + } 17401 + } 16584 17402 }, 16585 17403 "node_modules/react-remove-scroll": { 16586 17404 "version": "2.6.3", ··· 16746 17564 "node": ">= 12.13.0" 16747 17565 } 16748 17566 }, 17567 + "node_modules/recharts": { 17568 + "version": "3.7.0", 17569 + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", 17570 + "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==", 17571 + "license": "MIT", 17572 + "workspaces": [ 17573 + "www" 17574 + ], 17575 + "dependencies": { 17576 + "@reduxjs/toolkit": "1.x.x || 2.x.x", 17577 + "clsx": "^2.1.1", 17578 + "decimal.js-light": "^2.5.1", 17579 + "es-toolkit": "^1.39.3", 17580 + "eventemitter3": "^5.0.1", 17581 + "immer": "^10.1.1", 17582 + "react-redux": "8.x.x || 9.x.x", 17583 + "reselect": "5.1.1", 17584 + "tiny-invariant": "^1.3.3", 17585 + "use-sync-external-store": "^1.2.2", 17586 + "victory-vendor": "^37.0.2" 17587 + }, 17588 + "engines": { 17589 + "node": ">=18" 17590 + }, 17591 + "peerDependencies": { 17592 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", 17593 + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", 17594 + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 17595 + } 17596 + }, 17597 + "node_modules/recharts/node_modules/eventemitter3": { 17598 + "version": "5.0.4", 17599 + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", 17600 + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", 17601 + "license": "MIT" 17602 + }, 16749 17603 "node_modules/recma-build-jsx": { 16750 17604 "version": "1.0.0", 16751 17605 "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", ··· 16837 17691 }, 16838 17692 "engines": { 16839 17693 "node": ">=12" 17694 + } 17695 + }, 17696 + "node_modules/redux": { 17697 + "version": "5.0.1", 17698 + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", 17699 + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", 17700 + "license": "MIT" 17701 + }, 17702 + "node_modules/redux-thunk": { 17703 + "version": "3.1.0", 17704 + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", 17705 + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", 17706 + "license": "MIT", 17707 + "peerDependencies": { 17708 + "redux": "^5.0.0" 16840 17709 } 16841 17710 }, 16842 17711 "node_modules/reflect.getprototypeof": { ··· 17102 17971 "engines": { 17103 17972 "node": ">=8.6.0" 17104 17973 } 17974 + }, 17975 + "node_modules/reselect": { 17976 + "version": "5.1.1", 17977 + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", 17978 + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", 17979 + "license": "MIT" 17105 17980 }, 17106 17981 "node_modules/resolve": { 17107 17982 "version": "1.22.11", ··· 17674 18549 "node_modules/sisteransi": { 17675 18550 "version": "1.0.5", 17676 18551 "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", 17677 - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", 17678 - "dev": true 18552 + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" 17679 18553 }, 17680 18554 "node_modules/sonic-boom": { 17681 18555 "version": "3.8.1", ··· 18146 19020 "next-tick": "1" 18147 19021 } 18148 19022 }, 19023 + "node_modules/tiny-invariant": { 19024 + "version": "1.3.3", 19025 + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", 19026 + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", 19027 + "license": "MIT" 19028 + }, 18149 19029 "node_modules/tinyglobby": { 18150 19030 "version": "0.2.15", 18151 19031 "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", ··· 18854 19734 "funding": { 18855 19735 "type": "opencollective", 18856 19736 "url": "https://opencollective.com/unified" 19737 + } 19738 + }, 19739 + "node_modules/victory-vendor": { 19740 + "version": "37.3.6", 19741 + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", 19742 + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", 19743 + "license": "MIT AND ISC", 19744 + "dependencies": { 19745 + "@types/d3-array": "^3.0.3", 19746 + "@types/d3-ease": "^3.0.0", 19747 + "@types/d3-interpolate": "^3.0.1", 19748 + "@types/d3-scale": "^4.0.2", 19749 + "@types/d3-shape": "^3.1.0", 19750 + "@types/d3-time": "^3.0.0", 19751 + "@types/d3-timer": "^3.0.0", 19752 + "d3-array": "^3.1.6", 19753 + "d3-ease": "^3.0.1", 19754 + "d3-interpolate": "^3.0.1", 19755 + "d3-scale": "^4.0.2", 19756 + "d3-shape": "^3.1.0", 19757 + "d3-time": "^3.0.0", 19758 + "d3-timer": "^3.0.1" 18857 19759 } 18858 19760 }, 18859 19761 "node_modules/w3c-keyname": {
+2
package.json
··· 43 43 "@rocicorp/undo": "^0.2.1", 44 44 "@supabase/ssr": "^0.3.0", 45 45 "@supabase/supabase-js": "^2.43.2", 46 + "@tinybirdco/sdk": "^0.0.55", 46 47 "@tiptap/core": "^2.11.5", 47 48 "@types/mdx": "^2.0.13", 48 49 "@vercel/analytics": "^1.5.0", ··· 77 78 "react-day-picker": "^9.3.0", 78 79 "react-dom": "19.2.1", 79 80 "react-use-measure": "^2.1.1", 81 + "recharts": "^3.7.0", 80 82 "redlock": "^5.0.0-beta.2", 81 83 "rehype-parse": "^9.0.0", 82 84 "rehype-remark": "^10.0.0",
+9
tinybird.config.mjs
··· 1 + /** @type {import("@tinybirdco/sdk").TinybirdConfig} */ 2 + const tinybirdConfig = { 3 + include: ["lib/tinybird.ts"], 4 + token: process.env.TINYBIRD_TOKEN, 5 + baseUrl: process.env.TINYBIRD_URL, 6 + devMode: "branch", // or "local" if you want to run the project locally (Tinybird Local required) 7 + }; 8 + 9 + export default tinybirdConfig;