@sketchlog/react 1.2.3

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/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # `@sketchlog/react`
2
+
3
+ React components for a live SketchLog stream.
4
+
5
+ ```tsx
6
+ import {
7
+ SketchLogProvider,
8
+ QuantileHeatmap,
9
+ CardinalitySparkline,
10
+ } from "@sketchlog/react";
11
+
12
+ export function Dashboard() {
13
+ return (
14
+ <SketchLogProvider url="wss://metrics.example/v1/streams/api/ws">
15
+ <QuantileHeatmap />
16
+ <CardinalitySparkline />
17
+ </SketchLogProvider>
18
+ );
19
+ }
20
+ ```
21
+
22
+ For authenticated browser connections, have a same-site gateway set the
23
+ `sketchlog_auth` cookie with `HttpOnly`, `Secure`, and an appropriate
24
+ `SameSite` policy before opening the WebSocket. Tokens are not placed in URLs.
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface CDFCurveProps {
3
+ width?: number;
4
+ height?: number;
5
+ color?: string;
6
+ className?: string;
7
+ }
8
+ export declare const CDFCurve: React.FC<CDFCurveProps>;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface CardinalitySparklineProps {
3
+ width?: number;
4
+ height?: number;
5
+ color?: string;
6
+ historySize?: number;
7
+ className?: string;
8
+ }
9
+ export declare const CardinalitySparkline: React.FC<CardinalitySparklineProps>;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface QuantileHeatmapProps {
3
+ width?: number;
4
+ height?: number;
5
+ colorScheme?: readonly string[] | string | ((value: number) => string);
6
+ historySize?: number;
7
+ className?: string;
8
+ }
9
+ export declare const QuantileHeatmap: React.FC<QuantileHeatmapProps>;
@@ -0,0 +1,3 @@
1
+ import type { SketchLogContextType } from '../types';
2
+ export declare const SketchLogContext: import("react").Context<SketchLogContextType>;
3
+ export declare const useSketchLog: () => SketchLogContextType;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { ReactNode } from 'react';
3
+ export interface SketchLogProviderProps {
4
+ url: string;
5
+ children: ReactNode;
6
+ }
7
+ export declare const SketchLogProvider: React.FC<SketchLogProviderProps>;
@@ -0,0 +1,2 @@
1
+ import type { SketchLogCounter } from './types';
2
+ export declare function counterToNumber(value: SketchLogCounter): number;
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("react");c=s(c,1);let l=require("react-use-websocket");l=s(l,1);let u=require("react/jsx-runtime"),d=require("d3");d=s(d,1);var f=(0,c.createContext)({state:null,isConnected:!1,error:null}),p=()=>(0,c.useContext)(f),m=({url:e,children:t})=>{let[n,r]=(0,c.useState)(null),[i,a]=(0,c.useState)(null),{lastJsonMessage:o,readyState:s}=(0,l.default)(e,{shouldReconnect:()=>!0,reconnectAttempts:10,reconnectInterval:3e3,onError:()=>a(Error(`WebSocket connection error`))});(0,c.useEffect)(()=>{if(!o)return;let e=window.setTimeout(()=>{`error`in o?(a(Error(o.error)),r(null)):(r(o),a(null))},0);return()=>window.clearTimeout(e)},[o]);let d=s===1;return(0,u.jsx)(f.Provider,{value:{state:n,isConnected:d,error:i},children:t})};function h(e){let t=typeof e==`string`?Number(e):e;return Number.isFinite(t)&&t>=0?t:0}function g(e,t){let n=(1+e)/(1-e);return 2/(1+n)*n**+t}var _=({width:e=600,height:t=300,color:n=`#8b5cf6`,className:r=``})=>{let{state:i,isConnected:a}=p(),o=(0,c.useRef)(null),s=(0,c.useMemo)(()=>{if(!i||!i.latency)return[];let e=i.latency,t=0,n=[],r=h(e.count);if(r===0)return[];let a=Object.keys(e.negative).map(Number).sort((e,t)=>t-e);for(let i of a)t+=h(e.negative[i]),n.push({x:-g(e.alpha,i),y:t/r});h(e.zero_count)>0&&(t+=h(e.zero_count),n.push({x:0,y:t/r}));let o=Object.keys(e.positive).map(Number).sort((e,t)=>e-t);for(let i of o)t+=h(e.positive[i]),n.push({x:g(e.alpha,i),y:t/r});return n.length>0&&(n[n.length-1].y=1),n},[i]);return(0,c.useEffect)(()=>{if(!o.current||s.length===0)return;let r=d.select(o.current);r.selectAll(`*`).remove();let i={top:20,right:30,bottom:40,left:50},a=e-i.left-i.right,c=t-i.top-i.bottom,l=d.scaleLog().domain([Math.max(.1,s[0].x>0?s[0].x:.1),Math.max(.1,d.max(s,e=>e.x)||100)]).range([0,a]).clamp(!0),u=d.scaleLinear().domain([0,1]).range([c,0]),f=r.append(`g`).attr(`transform`,`translate(${i.left},${i.top})`);f.append(`g`).attr(`class`,`grid`).attr(`transform`,`translate(0,${c})`).call(d.axisBottom(l).ticks(5).tickSize(-c).tickFormat(()=>``)).attr(`stroke-opacity`,.1).attr(`stroke`,`currentColor`),f.append(`g`).attr(`class`,`grid`).call(d.axisLeft(u).ticks(5).tickSize(-a).tickFormat(()=>``)).attr(`stroke-opacity`,.1).attr(`stroke`,`currentColor`),f.append(`g`).attr(`transform`,`translate(0,${c})`).call(d.axisBottom(l).ticks(5,`~s`)).attr(`color`,`rgba(255, 255, 255, 0.5)`),f.append(`g`).call(d.axisLeft(u).tickFormat(d.format(`.0%`))).attr(`color`,`rgba(255, 255, 255, 0.5)`);let p=d.line().x(e=>l(Math.max(.1,e.x))).y(e=>u(e.y)).curve(d.curveStepAfter),m=d.area().x(e=>l(Math.max(.1,e.x))).y0(c).y1(e=>u(e.y)).curve(d.curveStepAfter),h=`cdf-gradient-${Math.random().toString(36).substring(2)}`,g=r.append(`defs`).append(`linearGradient`).attr(`id`,h).attr(`x1`,`0%`).attr(`y1`,`0%`).attr(`x2`,`0%`).attr(`y2`,`100%`);g.append(`stop`).attr(`offset`,`0%`).attr(`stop-color`,n).attr(`stop-opacity`,.4),g.append(`stop`).attr(`offset`,`100%`).attr(`stop-color`,n).attr(`stop-opacity`,0),f.append(`path`).datum(s).attr(`fill`,`url(#${h})`).attr(`d`,m).style(`transition`,`d 0.5s ease`),f.append(`path`).datum(s).attr(`fill`,`none`).attr(`stroke`,n).attr(`stroke-width`,2).attr(`stroke-linejoin`,`round`).attr(`stroke-linecap`,`round`).attr(`d`,p).style(`transition`,`d 0.5s ease`)},[s,e,t,n]),(0,u.jsxs)(`div`,{className:`relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${r}`,children:[(0,u.jsx)(`h3`,{className:`text-white/90 font-medium mb-2 text-sm`,children:`Latency CDF`}),(0,u.jsxs)(`div`,{className:`absolute top-4 right-4 flex items-center gap-2`,children:[(0,u.jsxs)(`span`,{className:`relative flex h-2 w-2`,children:[a&&(0,u.jsx)(`span`,{className:`animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75`}),(0,u.jsx)(`span`,{className:`relative inline-flex rounded-full h-2 w-2 ${a?`bg-green-500`:`bg-red-500`}`})]}),(0,u.jsx)(`span`,{className:`text-xs text-white/50`,children:a?`LIVE`:`OFFLINE`})]}),(0,u.jsx)(`svg`,{ref:o,width:e,height:t,className:`w-full h-full text-white/80`,style:{minHeight:t}})]})};function v(e,t){let n=(1+e)/(1-e);return 2/(1+n)*n**+t}var y=({width:e=600,height:t=300,colorScheme:n=d.interpolateInferno,historySize:r=30,className:i=``})=>{let{state:a,isConnected:o}=p(),s=(0,c.useRef)(null),[l,f]=(0,c.useState)([]);return(0,c.useEffect)(()=>{if(!a||!a.latency)return;let e=a.latency,t=Date.now(),n=new Map,i=Object.keys(e.positive).map(Number);for(let t of i)n.set(t,h(e.positive[t]));let o=Object.keys(e.negative).map(Number);for(let t of o)n.set(-t,(n.get(-t)||0)+h(e.negative[t]));h(e.zero_count)>0&&n.set(0,(n.get(0)||0)+h(e.zero_count));let s=window.setTimeout(()=>{f(i=>[...i,{time:t,total:h(e.count),bins:n}].slice(-r))},0);return()=>window.clearTimeout(s)},[a,r]),(0,c.useEffect)(()=>{if(!s.current||l.length===0)return;let i=d.select(s.current);i.selectAll(`*`).remove();let o={top:20,right:10,bottom:30,left:50},c=e-o.left-o.right,u=t-o.top-o.bottom,f=1/0,p=-1/0,m=0,h=[];for(let e=1;e<l.length;e++){let t=l[e-1],n=l[e],r=new Map;for(let[e,i]of n.bins.entries()){let n=t.bins.get(e)||0,a=Math.max(0,i-n);a>0&&(r.set(e,a),f=Math.min(f,e),p=Math.max(p,e),m=Math.max(m,a))}h.push({time:n.time,bins:r})}if(h.length===0||p===-1/0)return;let g=a?.latency?.alpha||.01,_=d.scaleTime().domain([l[0].time,l[l.length-1].time]).range([0,c]),y=d.scaleLinear().domain([f-1,p+1]).range([u,0]),b;b=typeof n==`function`?n:typeof n==`string`?d.interpolateRgb(`rgba(255,255,255,0)`,n):d.piecewise(d.interpolateRgb,[...n]);let x=d.scaleSequential(b).domain([0,m]),S=i.append(`g`).attr(`transform`,`translate(${o.left},${o.top})`),C=c/Math.max(1,r-1),w=Math.abs(y(f)-y(f+1));h.forEach(e=>{for(let[t,n]of e.bins.entries())S.append(`rect`).attr(`x`,_(e.time)-C).attr(`y`,y(t)-w/2).attr(`width`,C+1).attr(`height`,w+1).attr(`fill`,x(n)).attr(`rx`,2).attr(`ry`,2).attr(`opacity`,.9)}),S.append(`g`).attr(`transform`,`translate(0,${u})`).call(d.axisBottom(_).ticks(5)).attr(`color`,`rgba(255, 255, 255, 0.5)`);let T=d.axisLeft(y).tickValues(d.range(f,p+1,Math.max(1,Math.floor((p-f)/5)))).tickFormat(e=>d.format(`.1f`)(v(g,e)));S.append(`g`).call(T).attr(`color`,`rgba(255, 255, 255, 0.5)`)},[l,e,t,n,a,r]),(0,u.jsxs)(`div`,{className:`relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${i}`,children:[(0,u.jsx)(`h3`,{className:`text-white/90 font-medium mb-2 text-sm`,children:`Quantile Heatmap (Live)`}),(0,u.jsx)(`div`,{className:`absolute top-4 right-4 flex items-center gap-2`,children:(0,u.jsxs)(`span`,{className:`relative flex h-2 w-2`,children:[o&&(0,u.jsx)(`span`,{className:`animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75`}),(0,u.jsx)(`span`,{className:`relative inline-flex rounded-full h-2 w-2 ${o?`bg-blue-500`:`bg-red-500`}`})]})}),(0,u.jsx)(`svg`,{ref:s,width:e,height:t,className:`w-full h-full text-white/80`,style:{minHeight:t}})]})},b=({width:e=300,height:t=100,color:n=`#10b981`,historySize:r=60,className:i=``})=>{let{state:a}=p(),o=(0,c.useRef)(null),[s,l]=(0,c.useState)([]);return(0,c.useEffect)(()=>{if(!a)return;let e=h(a.metrics?.unique_count??0),t=Date.now(),n=window.setTimeout(()=>{l(n=>[...n,{time:t,value:e}].slice(-r))},0);return()=>window.clearTimeout(n)},[a,r]),(0,c.useEffect)(()=>{if(!o.current||s.length<2)return;let r=d.select(o.current);r.selectAll(`*`).remove();let i={top:5,right:5,bottom:5,left:5},a=e-i.left-i.right,c=t-i.top-i.bottom,l=d.scaleTime().domain(d.extent(s,e=>e.time)).range([0,a]),u=d.scaleLinear().domain([0,d.max(s,e=>e.value)||10]).range([c,0]),f=r.append(`g`).attr(`transform`,`translate(${i.left},${i.top})`),p=d.line().x(e=>l(e.time)).y(e=>u(e.value)).curve(d.curveMonotoneX),m=d.area().x(e=>l(e.time)).y0(c).y1(e=>u(e.value)).curve(d.curveMonotoneX),h=`spark-gradient-${Math.random().toString(36).substring(2)}`,g=r.append(`defs`).append(`linearGradient`).attr(`id`,h).attr(`x1`,`0%`).attr(`y1`,`0%`).attr(`x2`,`0%`).attr(`y2`,`100%`);g.append(`stop`).attr(`offset`,`0%`).attr(`stop-color`,n).attr(`stop-opacity`,.3),g.append(`stop`).attr(`offset`,`100%`).attr(`stop-color`,n).attr(`stop-opacity`,0),f.append(`path`).datum(s).attr(`fill`,`url(#${h})`).attr(`d`,m).style(`transition`,`d 0.3s linear`),f.append(`path`).datum(s).attr(`fill`,`none`).attr(`stroke`,n).attr(`stroke-width`,2).attr(`stroke-linecap`,`round`).attr(`d`,p).style(`transition`,`d 0.3s linear`);let _=s[s.length-1].value;r.append(`text`).attr(`x`,i.left).attr(`y`,i.top+15).text(d.format(`~s`)(_)).attr(`fill`,`white`).attr(`font-size`,`14px`).attr(`font-weight`,`600`)},[s,e,t,n]),(0,u.jsxs)(`div`,{className:`relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-2 shadow-xl flex flex-col ${i}`,children:[(0,u.jsx)(`h3`,{className:`text-white/70 font-medium mb-1 text-xs px-2 pt-1`,children:`Estimated Cardinality`}),(0,u.jsx)(`svg`,{ref:o,width:e,height:t,className:`w-full text-white/80`,style:{minHeight:t}})]})};exports.CDFCurve=_,exports.CardinalitySparkline=b,exports.QuantileHeatmap=y,exports.SketchLogContext=f,exports.SketchLogProvider=m,exports.useSketchLog=p;
@@ -0,0 +1,6 @@
1
+ export * from './types';
2
+ export * from './components/SketchLogProvider';
3
+ export * from './components/SketchLogContext';
4
+ export * from './components/CDFCurve';
5
+ export * from './components/QuantileHeatmap';
6
+ export * from './components/CardinalitySparkline';
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ import { createContext as e, useContext as t, useEffect as n, useMemo as r, useRef as i, useState as a } from "react";
2
+ import o from "react-use-websocket";
3
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
4
+ import * as l from "d3";
5
+ //#region src/components/SketchLogContext.ts
6
+ var u = e({
7
+ state: null,
8
+ isConnected: !1,
9
+ error: null
10
+ }), d = () => t(u), f = ({ url: e, children: t }) => {
11
+ let [r, i] = a(null), [c, l] = a(null), { lastJsonMessage: d, readyState: f } = o(e, {
12
+ shouldReconnect: () => !0,
13
+ reconnectAttempts: 10,
14
+ reconnectInterval: 3e3,
15
+ onError: () => l(/* @__PURE__ */ Error("WebSocket connection error"))
16
+ });
17
+ n(() => {
18
+ if (!d) return;
19
+ let e = window.setTimeout(() => {
20
+ "error" in d ? (l(Error(d.error)), i(null)) : (i(d), l(null));
21
+ }, 0);
22
+ return () => window.clearTimeout(e);
23
+ }, [d]);
24
+ let p = f === 1;
25
+ return /* @__PURE__ */ s(u.Provider, {
26
+ value: {
27
+ state: r,
28
+ isConnected: p,
29
+ error: c
30
+ },
31
+ children: t
32
+ });
33
+ };
34
+ //#endregion
35
+ //#region src/counter.ts
36
+ function p(e) {
37
+ let t = typeof e == "string" ? Number(e) : e;
38
+ return Number.isFinite(t) && t >= 0 ? t : 0;
39
+ }
40
+ //#endregion
41
+ //#region src/components/CDFCurve.tsx
42
+ function m(e, t) {
43
+ let n = (1 + e) / (1 - e);
44
+ return 2 / (1 + n) * n ** +t;
45
+ }
46
+ var h = ({ width: e = 600, height: t = 300, color: a = "#8b5cf6", className: o = "" }) => {
47
+ let { state: u, isConnected: f } = d(), h = i(null), g = r(() => {
48
+ if (!u || !u.latency) return [];
49
+ let e = u.latency, t = 0, n = [], r = p(e.count);
50
+ if (r === 0) return [];
51
+ let i = Object.keys(e.negative).map(Number).sort((e, t) => t - e);
52
+ for (let a of i) t += p(e.negative[a]), n.push({
53
+ x: -m(e.alpha, a),
54
+ y: t / r
55
+ });
56
+ p(e.zero_count) > 0 && (t += p(e.zero_count), n.push({
57
+ x: 0,
58
+ y: t / r
59
+ }));
60
+ let a = Object.keys(e.positive).map(Number).sort((e, t) => e - t);
61
+ for (let i of a) t += p(e.positive[i]), n.push({
62
+ x: m(e.alpha, i),
63
+ y: t / r
64
+ });
65
+ return n.length > 0 && (n[n.length - 1].y = 1), n;
66
+ }, [u]);
67
+ return n(() => {
68
+ if (!h.current || g.length === 0) return;
69
+ let n = l.select(h.current);
70
+ n.selectAll("*").remove();
71
+ let r = {
72
+ top: 20,
73
+ right: 30,
74
+ bottom: 40,
75
+ left: 50
76
+ }, i = e - r.left - r.right, o = t - r.top - r.bottom, s = l.scaleLog().domain([Math.max(.1, g[0].x > 0 ? g[0].x : .1), Math.max(.1, l.max(g, (e) => e.x) || 100)]).range([0, i]).clamp(!0), c = l.scaleLinear().domain([0, 1]).range([o, 0]), u = n.append("g").attr("transform", `translate(${r.left},${r.top})`);
77
+ u.append("g").attr("class", "grid").attr("transform", `translate(0,${o})`).call(l.axisBottom(s).ticks(5).tickSize(-o).tickFormat(() => "")).attr("stroke-opacity", .1).attr("stroke", "currentColor"), u.append("g").attr("class", "grid").call(l.axisLeft(c).ticks(5).tickSize(-i).tickFormat(() => "")).attr("stroke-opacity", .1).attr("stroke", "currentColor"), u.append("g").attr("transform", `translate(0,${o})`).call(l.axisBottom(s).ticks(5, "~s")).attr("color", "rgba(255, 255, 255, 0.5)"), u.append("g").call(l.axisLeft(c).tickFormat(l.format(".0%"))).attr("color", "rgba(255, 255, 255, 0.5)");
78
+ let d = l.line().x((e) => s(Math.max(.1, e.x))).y((e) => c(e.y)).curve(l.curveStepAfter), f = l.area().x((e) => s(Math.max(.1, e.x))).y0(o).y1((e) => c(e.y)).curve(l.curveStepAfter), p = `cdf-gradient-${Math.random().toString(36).substring(2)}`, m = n.append("defs").append("linearGradient").attr("id", p).attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
79
+ m.append("stop").attr("offset", "0%").attr("stop-color", a).attr("stop-opacity", .4), m.append("stop").attr("offset", "100%").attr("stop-color", a).attr("stop-opacity", 0), u.append("path").datum(g).attr("fill", `url(#${p})`).attr("d", f).style("transition", "d 0.5s ease"), u.append("path").datum(g).attr("fill", "none").attr("stroke", a).attr("stroke-width", 2).attr("stroke-linejoin", "round").attr("stroke-linecap", "round").attr("d", d).style("transition", "d 0.5s ease");
80
+ }, [
81
+ g,
82
+ e,
83
+ t,
84
+ a
85
+ ]), /* @__PURE__ */ c("div", {
86
+ className: `relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${o}`,
87
+ children: [
88
+ /* @__PURE__ */ s("h3", {
89
+ className: "text-white/90 font-medium mb-2 text-sm",
90
+ children: "Latency CDF"
91
+ }),
92
+ /* @__PURE__ */ c("div", {
93
+ className: "absolute top-4 right-4 flex items-center gap-2",
94
+ children: [/* @__PURE__ */ c("span", {
95
+ className: "relative flex h-2 w-2",
96
+ children: [f && /* @__PURE__ */ s("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }), /* @__PURE__ */ s("span", { className: `relative inline-flex rounded-full h-2 w-2 ${f ? "bg-green-500" : "bg-red-500"}` })]
97
+ }), /* @__PURE__ */ s("span", {
98
+ className: "text-xs text-white/50",
99
+ children: f ? "LIVE" : "OFFLINE"
100
+ })]
101
+ }),
102
+ /* @__PURE__ */ s("svg", {
103
+ ref: h,
104
+ width: e,
105
+ height: t,
106
+ className: "w-full h-full text-white/80",
107
+ style: { minHeight: t }
108
+ })
109
+ ]
110
+ });
111
+ };
112
+ //#endregion
113
+ //#region src/components/QuantileHeatmap.tsx
114
+ function g(e, t) {
115
+ let n = (1 + e) / (1 - e);
116
+ return 2 / (1 + n) * n ** +t;
117
+ }
118
+ var _ = ({ width: e = 600, height: t = 300, colorScheme: r = l.interpolateInferno, historySize: o = 30, className: u = "" }) => {
119
+ let { state: f, isConnected: m } = d(), h = i(null), [_, v] = a([]);
120
+ return n(() => {
121
+ if (!f || !f.latency) return;
122
+ let e = f.latency, t = Date.now(), n = /* @__PURE__ */ new Map(), r = Object.keys(e.positive).map(Number);
123
+ for (let t of r) n.set(t, p(e.positive[t]));
124
+ let i = Object.keys(e.negative).map(Number);
125
+ for (let t of i) n.set(-t, (n.get(-t) || 0) + p(e.negative[t]));
126
+ p(e.zero_count) > 0 && n.set(0, (n.get(0) || 0) + p(e.zero_count));
127
+ let a = window.setTimeout(() => {
128
+ v((r) => [...r, {
129
+ time: t,
130
+ total: p(e.count),
131
+ bins: n
132
+ }].slice(-o));
133
+ }, 0);
134
+ return () => window.clearTimeout(a);
135
+ }, [f, o]), n(() => {
136
+ if (!h.current || _.length === 0) return;
137
+ let n = l.select(h.current);
138
+ n.selectAll("*").remove();
139
+ let i = {
140
+ top: 20,
141
+ right: 10,
142
+ bottom: 30,
143
+ left: 50
144
+ }, a = e - i.left - i.right, s = t - i.top - i.bottom, c = Infinity, u = -Infinity, d = 0, p = [];
145
+ for (let e = 1; e < _.length; e++) {
146
+ let t = _[e - 1], n = _[e], r = /* @__PURE__ */ new Map();
147
+ for (let [e, i] of n.bins.entries()) {
148
+ let n = t.bins.get(e) || 0, a = Math.max(0, i - n);
149
+ a > 0 && (r.set(e, a), c = Math.min(c, e), u = Math.max(u, e), d = Math.max(d, a));
150
+ }
151
+ p.push({
152
+ time: n.time,
153
+ bins: r
154
+ });
155
+ }
156
+ if (p.length === 0 || u === -Infinity) return;
157
+ let m = f?.latency?.alpha || .01, v = l.scaleTime().domain([_[0].time, _[_.length - 1].time]).range([0, a]), y = l.scaleLinear().domain([c - 1, u + 1]).range([s, 0]), b;
158
+ b = typeof r == "function" ? r : typeof r == "string" ? l.interpolateRgb("rgba(255,255,255,0)", r) : l.piecewise(l.interpolateRgb, [...r]);
159
+ let x = l.scaleSequential(b).domain([0, d]), S = n.append("g").attr("transform", `translate(${i.left},${i.top})`), C = a / Math.max(1, o - 1), w = Math.abs(y(c) - y(c + 1));
160
+ p.forEach((e) => {
161
+ for (let [t, n] of e.bins.entries()) S.append("rect").attr("x", v(e.time) - C).attr("y", y(t) - w / 2).attr("width", C + 1).attr("height", w + 1).attr("fill", x(n)).attr("rx", 2).attr("ry", 2).attr("opacity", .9);
162
+ }), S.append("g").attr("transform", `translate(0,${s})`).call(l.axisBottom(v).ticks(5)).attr("color", "rgba(255, 255, 255, 0.5)");
163
+ let T = l.axisLeft(y).tickValues(l.range(c, u + 1, Math.max(1, Math.floor((u - c) / 5)))).tickFormat((e) => l.format(".1f")(g(m, e)));
164
+ S.append("g").call(T).attr("color", "rgba(255, 255, 255, 0.5)");
165
+ }, [
166
+ _,
167
+ e,
168
+ t,
169
+ r,
170
+ f,
171
+ o
172
+ ]), /* @__PURE__ */ c("div", {
173
+ className: `relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-4 shadow-2xl ${u}`,
174
+ children: [
175
+ /* @__PURE__ */ s("h3", {
176
+ className: "text-white/90 font-medium mb-2 text-sm",
177
+ children: "Quantile Heatmap (Live)"
178
+ }),
179
+ /* @__PURE__ */ s("div", {
180
+ className: "absolute top-4 right-4 flex items-center gap-2",
181
+ children: /* @__PURE__ */ c("span", {
182
+ className: "relative flex h-2 w-2",
183
+ children: [m && /* @__PURE__ */ s("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" }), /* @__PURE__ */ s("span", { className: `relative inline-flex rounded-full h-2 w-2 ${m ? "bg-blue-500" : "bg-red-500"}` })]
184
+ })
185
+ }),
186
+ /* @__PURE__ */ s("svg", {
187
+ ref: h,
188
+ width: e,
189
+ height: t,
190
+ className: "w-full h-full text-white/80",
191
+ style: { minHeight: t }
192
+ })
193
+ ]
194
+ });
195
+ }, v = ({ width: e = 300, height: t = 100, color: r = "#10b981", historySize: o = 60, className: u = "" }) => {
196
+ let { state: f } = d(), m = i(null), [h, g] = a([]);
197
+ return n(() => {
198
+ if (!f) return;
199
+ let e = p(f.metrics?.unique_count ?? 0), t = Date.now(), n = window.setTimeout(() => {
200
+ g((n) => [...n, {
201
+ time: t,
202
+ value: e
203
+ }].slice(-o));
204
+ }, 0);
205
+ return () => window.clearTimeout(n);
206
+ }, [f, o]), n(() => {
207
+ if (!m.current || h.length < 2) return;
208
+ let n = l.select(m.current);
209
+ n.selectAll("*").remove();
210
+ let i = {
211
+ top: 5,
212
+ right: 5,
213
+ bottom: 5,
214
+ left: 5
215
+ }, a = e - i.left - i.right, o = t - i.top - i.bottom, s = l.scaleTime().domain(l.extent(h, (e) => e.time)).range([0, a]), c = l.scaleLinear().domain([0, l.max(h, (e) => e.value) || 10]).range([o, 0]), u = n.append("g").attr("transform", `translate(${i.left},${i.top})`), d = l.line().x((e) => s(e.time)).y((e) => c(e.value)).curve(l.curveMonotoneX), f = l.area().x((e) => s(e.time)).y0(o).y1((e) => c(e.value)).curve(l.curveMonotoneX), p = `spark-gradient-${Math.random().toString(36).substring(2)}`, g = n.append("defs").append("linearGradient").attr("id", p).attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
216
+ g.append("stop").attr("offset", "0%").attr("stop-color", r).attr("stop-opacity", .3), g.append("stop").attr("offset", "100%").attr("stop-color", r).attr("stop-opacity", 0), u.append("path").datum(h).attr("fill", `url(#${p})`).attr("d", f).style("transition", "d 0.3s linear"), u.append("path").datum(h).attr("fill", "none").attr("stroke", r).attr("stroke-width", 2).attr("stroke-linecap", "round").attr("d", d).style("transition", "d 0.3s linear");
217
+ let _ = h[h.length - 1].value;
218
+ n.append("text").attr("x", i.left).attr("y", i.top + 15).text(l.format("~s")(_)).attr("fill", "white").attr("font-size", "14px").attr("font-weight", "600");
219
+ }, [
220
+ h,
221
+ e,
222
+ t,
223
+ r
224
+ ]), /* @__PURE__ */ c("div", {
225
+ className: `relative rounded-xl overflow-hidden backdrop-blur-md bg-white/5 border border-white/10 p-2 shadow-xl flex flex-col ${u}`,
226
+ children: [/* @__PURE__ */ s("h3", {
227
+ className: "text-white/70 font-medium mb-1 text-xs px-2 pt-1",
228
+ children: "Estimated Cardinality"
229
+ }), /* @__PURE__ */ s("svg", {
230
+ ref: m,
231
+ width: e,
232
+ height: t,
233
+ className: "w-full text-white/80",
234
+ style: { minHeight: t }
235
+ })]
236
+ });
237
+ };
238
+ //#endregion
239
+ export { h as CDFCurve, v as CardinalitySparkline, _ as QuantileHeatmap, u as SketchLogContext, f as SketchLogProvider, d as useSketchLog };
@@ -0,0 +1,42 @@
1
+ export type SketchLogCounter = number | string;
2
+ export interface SketchLogLatencyState {
3
+ alpha: number;
4
+ zero_count: SketchLogCounter;
5
+ count: SketchLogCounter;
6
+ min: number | null;
7
+ max: number | null;
8
+ positive: Record<string, SketchLogCounter>;
9
+ negative: Record<string, SketchLogCounter>;
10
+ }
11
+ export interface SketchLogEventsState {
12
+ width: number;
13
+ depth: number;
14
+ table: SketchLogCounter[][];
15
+ total: SketchLogCounter;
16
+ }
17
+ export interface SketchLogUniquesState {
18
+ precision: number;
19
+ registers: number[];
20
+ }
21
+ export interface SketchLogState {
22
+ version: number;
23
+ total: SketchLogCounter;
24
+ deterministic: boolean;
25
+ latency: SketchLogLatencyState;
26
+ events: SketchLogEventsState;
27
+ uniques: SketchLogUniquesState;
28
+ metrics?: {
29
+ p50: number;
30
+ p95: number;
31
+ p99: number;
32
+ p99_9: number;
33
+ unique_count: SketchLogCounter;
34
+ total_events: SketchLogCounter;
35
+ memory_footprint_bytes: SketchLogCounter;
36
+ };
37
+ }
38
+ export interface SketchLogContextType {
39
+ state: SketchLogState | null;
40
+ isConnected: boolean;
41
+ error: Error | null;
42
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@sketchlog/react",
3
+ "private": false,
4
+ "version": "1.2.3",
5
+ "description": "React provider, hooks, and visualizations for SketchLog streams",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": ["dist", "README.md"],
18
+ "keywords": ["sketchlog", "react", "observability", "visualization"],
19
+ "author": "Bala Vignesh S",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/SBALAVIGNESH123/sketchlog.git",
24
+ "directory": "frontend/react-sketchlog"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/SBALAVIGNESH123/sketchlog/issues"
28
+ },
29
+ "homepage": "https://github.com/SBALAVIGNESH123/sketchlog/tree/main/frontend/react-sketchlog",
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "provenance": true
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "scripts": {
38
+ "dev": "vite",
39
+ "build": "tsc -b && vite build && tsc -p tsconfig.lib.json",
40
+ "lint": "eslint .",
41
+ "test": "vitest run",
42
+ "preview": "vite preview"
43
+ },
44
+ "peerDependencies": {
45
+ "d3": "^7.9.0",
46
+ "react": ">=18",
47
+ "react-dom": ">=18",
48
+ "react-use-websocket": "^4.13.0"
49
+ },
50
+ "devDependencies": {
51
+ "@eslint/js": "^10.0.1",
52
+ "@types/d3": "^7.4.3",
53
+ "@types/node": "^24.13.2",
54
+ "@types/react": "^19.2.17",
55
+ "@types/react-dom": "^19.2.3",
56
+ "@testing-library/react": "^16.3.0",
57
+ "@vitejs/plugin-react": "^6.0.2",
58
+ "eslint": "^10.5.0",
59
+ "eslint-plugin-react-hooks": "^7.1.1",
60
+ "eslint-plugin-react-refresh": "^0.5.3",
61
+ "globals": "^17.6.0",
62
+ "jsdom": "^26.1.0",
63
+ "typescript": "~6.0.2",
64
+ "typescript-eslint": "^8.61.0",
65
+ "vite": "^8.1.0",
66
+ "vitest": "^3.2.4"
67
+ }
68
+ }