@bonnard/mcp-charts 0.1.2 → 0.2.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolve/detect.ts","../src/resolve/pivot.ts","../src/resolve/fill-time.ts","../src/resolve/resolve.ts"],"sourcesContent":["// Auto-detect a sensible chart type from the data shape, when the caller didn't force one.\nimport type { ChartType, FieldMeta } from \"../types.js\";\n\nexport function detectChartType(fields: FieldMeta[]): ChartType {\n if (fields.some((f) => f.role === \"time\")) return \"line\"; // a time axis\n if (fields.some((f) => f.role === \"dimension\")) return \"bar\"; // a category breakdown\n return \"table\"; // a single metric (or unknown shape)\n}\n","/**\n * Pivot tall data to wide format for multi-series charts.\n *\n * Input (tall): [{ month, region: \"EU\", revenue: 29300 }, { month, region: \"US\", revenue: 22500 }]\n * Output (wide): [{ month, EU: 29300, US: 22500 }]\n */\nexport function pivotData(\n data: Record<string, unknown>[],\n xKey: string,\n pivotKey: string,\n valueKey: string,\n): { data: Record<string, unknown>[]; seriesKeys: string[]; collapsed: number } {\n const seriesKeys: string[] = [];\n const seen = new Set<string>();\n\n for (const row of data) {\n const raw = row[pivotKey];\n const pivotValue = raw == null || raw === \"\" ? \"(No value)\" : String(raw);\n if (!seen.has(pivotValue)) {\n seen.add(pivotValue);\n seriesKeys.push(pivotValue);\n }\n }\n\n // Sum on collision: multiple rows for the same (x, series) cell mean the source data was\n // unaggregated. Last-write-wins would silently drop data; summing matches what a GROUP BY\n // would have produced. `collapsed` counts the extra rows folded in, for a caller advisory.\n const groups = new Map<string, Record<string, unknown>>();\n let collapsed = 0;\n for (const row of data) {\n const xValue = String(row[xKey] ?? \"\");\n const raw = row[pivotKey];\n const pivotValue = raw == null || raw === \"\" ? \"(No value)\" : String(raw);\n if (!groups.has(xValue)) groups.set(xValue, { [xKey]: row[xKey] });\n const group = groups.get(xValue)!;\n // hasOwn, not `in`: a category literally named \"constructor\"/\"toString\" would otherwise\n // match an inherited prototype key and be mis-summed.\n if (Object.hasOwn(group, pivotValue)) {\n collapsed++;\n group[pivotValue] = (Number(group[pivotValue]) || 0) + (Number(row[valueKey]) || 0);\n } else {\n group[pivotValue] = row[valueKey];\n }\n }\n\n return { data: Array.from(groups.values()), seriesKeys, collapsed };\n}\n","// Fill missing time intervals so renderers show gaps correctly. Detects min/max dates,\n// generates all expected interval starts for the granularity, inserts null-measure rows\n// for missing periods. Pure; timezone-safe (treats Cube-style no-Z strings as UTC).\nimport type { TimeGranularity } from \"../types.js\";\n\nexport function fillMissingTimeIntervals(\n data: Record<string, unknown>[],\n xKey: string,\n measureKeys: string[],\n granularity: TimeGranularity,\n): Record<string, unknown>[] {\n if (data.length < 2) return data;\n\n const entries: { date: Date }[] = [];\n for (const row of data) {\n const date = parseUTC(String(row[xKey]));\n if (date) entries.push({ date });\n }\n if (entries.length < 2) return data;\n\n entries.sort((a, b) => a.date.getTime() - b.date.getTime());\n const min = entries[0]!.date;\n const max = entries[entries.length - 1]!.date;\n const allDates = generateSequence(min, max, granularity);\n\n // Bucket rows to the granularity (a 14:30 timestamp belongs to its day/month bucket, not to\n // an hour that the midnight-aligned sequence can never match). A bucket may hold several rows.\n // Week buckets anchor to the sequence start: UTC midnight of the min's day.\n const anchor = new Date(Date.UTC(min.getUTCFullYear(), min.getUTCMonth(), min.getUTCDate()));\n const key = (d: Date) => dateKey(d, granularity, anchor);\n const existing = new Map<string, Record<string, unknown>[]>();\n const emitted = new Set<Record<string, unknown>>();\n for (const row of data) {\n const date = parseUTC(String(row[xKey]));\n if (!date) continue;\n const k = key(date);\n const list = existing.get(k) ?? [];\n list.push(row);\n existing.set(k, list);\n }\n\n const result: Record<string, unknown>[] = [];\n for (const date of allDates) {\n const found = existing.get(key(date));\n if (found) {\n for (const row of found) {\n result.push(row);\n emitted.add(row);\n }\n } else {\n const nullRow: Record<string, unknown> = { [xKey]: toISOish(date) };\n for (const mk of measureKeys) nullRow[mk] = null;\n result.push(nullRow);\n }\n }\n // Gap-filling must never DROP data: keep any row the sequence didn't cover (unparseable x,\n // or a bucket outside the generated range).\n for (const row of data) if (!emitted.has(row)) result.push(row);\n return result;\n}\n\nfunction parseUTC(str: string): Date | null {\n let s = str.trim();\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/.test(s)) s = s.replace(\" \", \"T\"); // SQL-style datetime\n if (/^\\d{4}-\\d{2}-\\d{2}T/.test(s) && !s.endsWith(\"Z\") && !/[+-]\\d{2}:\\d{2}$/.test(s)) {\n s += \"Z\";\n } else if (/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) {\n s += \"T00:00:00.000Z\";\n }\n const d = new Date(s);\n return isNaN(d.getTime()) ? null : d;\n}\n\nconst WEEK_MS = 7 * 86_400_000;\n\n// Truncate a date to its granularity bucket. Weeks are anchored to the sequence start (the\n// sequence steps 7 days from `anchor`, not from ISO Mondays).\nfunction dateKey(d: Date, granularity: TimeGranularity, anchor: Date): string {\n const y = d.getUTCFullYear();\n switch (granularity) {\n case \"year\":\n return String(y);\n case \"quarter\":\n return `${y}-Q${Math.floor(d.getUTCMonth() / 3) + 1}`;\n case \"month\":\n return `${y}-${pad(d.getUTCMonth() + 1)}`;\n case \"week\":\n return `W${Math.floor((d.getTime() - anchor.getTime()) / WEEK_MS)}`;\n default:\n return `${y}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;\n }\n}\n\nfunction toISOish(d: Date): string {\n return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T00:00:00.000`;\n}\n\nfunction pad(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\nfunction generateSequence(start: Date, end: Date, granularity: TimeGranularity): Date[] {\n const dates: Date[] = [];\n const endTime = end.getTime();\n const MAX_INTERVALS = 10_000;\n let y = start.getUTCFullYear();\n let m = start.getUTCMonth();\n let d = start.getUTCDate();\n\n while (dates.length < MAX_INTERVALS) {\n const current = new Date(Date.UTC(y, m, d));\n if (current.getTime() > endTime) break;\n dates.push(current);\n switch (granularity) {\n case \"day\":\n d += 1;\n break;\n case \"week\":\n d += 7;\n break;\n case \"month\":\n m += 1;\n d = 1;\n break;\n case \"quarter\":\n m += 3;\n d = 1;\n break;\n case \"year\":\n y += 1;\n break;\n default:\n d += 1;\n break;\n }\n }\n return dates;\n}\n","// resolve() — the pure brain. Turns a ChartData (rows + optional typing + encode) into a\n// fully-specified, JSON-serializable ChartSpec: x / y / series, axis + column format\n// descriptors, legend, pivoting, time-gap filling. No IO, no host, no chart library.\nimport type {\n AxisSpec,\n ChartData,\n ChartSpec,\n ColumnSpec,\n Encode,\n FieldMeta,\n ReferenceLine,\n ResolveOptions,\n SeriesSpec,\n} from \"../types.js\";\nimport { inferFields, looksLikeLooseDate } from \"./infer.js\";\nimport { detectChartType } from \"./detect.js\";\nimport { pivotData } from \"./pivot.js\";\nimport { fillMissingTimeIntervals } from \"./fill-time.js\";\n\nconst HORIZONTAL_CATEGORY_THRESHOLD = 8;\nconst MAX_PIE_SLICES = 8; // rank cap: slices beyond this fold into \"Other\"\nconst MIN_PIE_FRACTION = 0.02; // share cap: slices under 2% of the total fold into \"Other\"\nconst MAX_BARS = 30; // categorical bars beyond this: keep the top N by value, drop the tail\nconst MAX_SERIES = 12; // distinct series beyond this: keep the top N by total, fold rest into \"Other\"\nconst MAX_POINTS = 2000; // line/area points beyond this: stride-downsample to bound the payload\nconst MAX_SCATTER_POINTS = 2000; // scatter points beyond this: sample down to keep the cloud legible\nconst FRACTION_MAX = 1.5; // percent columns: max |value| at or below this reads as a 0-1 fraction\n\n// Decide the percent scale ONCE per column. Guessing per value at render time flipped scale\n// mid-series (0.98 -> 98% next to 1.02 -> 1%); the column's magnitude decides instead.\nfunction isFractionScale(rows: Record<string, unknown>[], keys: string[]): boolean {\n let max = -Infinity;\n for (const r of rows) {\n for (const k of keys) {\n const v = r[k];\n if (v == null) continue;\n const n = Number(v);\n if (Number.isFinite(n) && Math.abs(n) > max) max = Math.abs(n);\n }\n }\n return max >= 0 && max <= FRACTION_MAX;\n}\n\nexport function resolve(data: ChartData, opts: ResolveOptions = {}): ChartSpec {\n const encode = data.encode ?? {};\n const fields = inferFields(data);\n const byName = new Map(fields.map((f) => [f.name, f]));\n\n // Transparency: never silently drop an encode mapping. Flag any encode column that isn't in the\n // result (a typo or stale name) so the agent can fix its next call; we still render what we can.\n const encodeRefs = (v?: string | string[]) => (v == null ? [] : Array.isArray(v) ? v : [v]);\n const unknownCols = [\n ...new Set(\n [\n encode.x,\n ...encodeRefs(encode.y),\n encode.series,\n ...encodeRefs(encode.y2),\n ...encodeRefs(encode.line),\n encode.size,\n ].filter((c): c is string => !!c && !byName.has(c)),\n ),\n ];\n if (unknownCols.length) {\n const msg = `Ignored unknown encode column${unknownCols.length > 1 ? \"s\" : \"\"} ${unknownCols.map((c) => `\"${c}\"`).join(\", \")}; available: ${fields.map((f) => f.name).join(\", \")}.`;\n if (opts.strict) throw new Error(msg);\n data.notes = [msg, ...(data.notes ?? [])];\n }\n\n // Scatter/bubble: x AND y are measures, one row = one point. Skip the dimension/aggregate path\n // entirely (aggregating would collapse the cloud) — handled by its own branch below.\n const isScatter = opts.chartType === \"scatter\";\n\n // Numeric grouping column: when EVERY column is numeric (no time/string dimension to be an\n // x-axis), the lowest-cardinality numeric acts as the x dimension (e.g. {year, revenue} ->\n // a bar over year) instead of degrading to a two-measure table. Tie on cardinality -> the\n // earliest column (SQL convention puts the GROUP BY key first). Not for scatter (both stay measures).\n if (\n !isScatter &&\n !encode.x &&\n data.rows.length > 1 &&\n fields.length >= 2 &&\n fields.every((f) => f.role === \"measure\")\n ) {\n const distinct = (k: string) => new Set(data.rows.map((r) => r[k])).size;\n let best = fields[0]!;\n for (const f of fields) if (distinct(f.name) < distinct(best.name)) best = f;\n best.role = \"dimension\";\n }\n\n // Coerce measure columns to numbers (a backend may return numeric strings).\n const measureNames = new Set(fields.filter((f) => f.role === \"measure\").map((f) => f.name));\n let rows: Record<string, unknown>[] = data.rows.map((row) => {\n const out: Record<string, unknown> = { ...row };\n for (const k of Object.keys(out)) {\n if (measureNames.has(k) && out[k] != null) {\n const n = Number(out[k]);\n if (!Number.isNaN(n)) out[k] = n;\n }\n }\n return out;\n });\n\n // Scatter/bubble: own branch — two measures as a point cloud, no aggregation/pivot/sort.\n if (isScatter) return withNotes(resolveScatter(rows, fields, encode, opts), data.notes);\n // Funnel: stages (a dimension) + a value, no axes — own branch.\n if (opts.chartType === \"funnel\") return withNotes(resolveFunnel(rows, fields, encode, opts), data.notes);\n // Waterfall: ordered steps with signed deltas; first/last (or a marked column) are totals.\n if (opts.chartType === \"waterfall\") return withNotes(resolveWaterfall(rows, fields, encode, opts), data.notes);\n\n // --- x-axis: encode wins, then first time field, then first dimension ---\n const timeField = fields.find((f) => f.role === \"time\");\n const dimField = fields.find((f) => f.role === \"dimension\");\n const xField: FieldMeta | undefined = encode.x ? byName.get(encode.x) : (timeField ?? dimField);\n const x = xField?.name ?? \"\";\n\n // --- measures (y) --- (never plot the x column as a measure)\n // y2 = measures pulled onto a secondary right axis (dual-axis combo); excluded from the left.\n const y2Names = encode.y2 ? (Array.isArray(encode.y2) ? encode.y2 : [encode.y2]) : [];\n // An explicit encode.y that names a column not in the result is already flagged as \"ignored\"\n // above; drop it here too so the spec doesn't carry a phantom series with no backing column\n // (the note and the spec then agree). The zero-series guard covers the \"all dropped\" case.\n const yNames = (\n encode.y\n ? (Array.isArray(encode.y) ? encode.y : [encode.y]).filter((n) => byName.has(n))\n : fields.filter((f) => f.role === \"measure\" && f.name !== x).map((f) => f.name)\n ).filter((n) => !y2Names.includes(n));\n\n let chartType = !opts.chartType || opts.chartType === \"auto\" ? detectChartType(fields) : opts.chartType;\n\n // No x-axis to plot against -> fall back to a table.\n if (!x && chartType !== \"table\") chartType = \"table\";\n\n // Tables are a RAW grid passthrough: every column, every row, in source order.\n // No pivot, no series selection, no time-fill — those are chart concerns and would\n // silently drop the text columns a table is meant to show.\n if (chartType === \"table\") {\n return {\n chartType: \"table\",\n data: rows,\n x: \"\",\n series: [],\n legend: false,\n ...(data.notes?.length && { notes: data.notes }),\n ...(opts.title && { title: opts.title }),\n columns: fields.map((f) => ({\n key: f.name,\n label: f.label ?? f.name,\n ...(f.format && { format: f.format }),\n ...(f.format === \"percent\" && { fraction: isFractionScale(rows, [f.name]) }),\n ...(f.currency && { currency: f.currency }),\n ...(f.granularity && { granularity: f.granularity }),\n })),\n };\n }\n\n // Normalize x values (nulls, booleans) for plotting.\n if (x) {\n const isBool = xField?.kind === \"boolean\";\n let allEmpty = rows.length > 0;\n for (const row of rows) {\n const v = row[x];\n if (v == null || v === \"\") row[x] = \"(No value)\";\n else {\n allEmpty = false;\n if (isBool) row[x] = v === true || v === \"true\" ? \"Yes\" : \"No\";\n }\n }\n // An entirely-null x column is a broken query (no axis to plot against), not a one-bar chart.\n if (allEmpty) throw new Error(`Column \"${x}\" is entirely empty — nothing to put on the x-axis.`);\n }\n\n // --- series: pivot a (time + one categorical dimension + one measure) into multi-series ---\n const pivotDim =\n encode.series ??\n (timeField && dimField && dimField.name !== x && dimField.kind === \"string\" ? dimField.name : undefined);\n\n const notes: string[] = [...(data.notes ?? [])];\n\n // Loose-date note (advisory only): a string x whose values look like non-ISO dates\n // (MM/DD/YYYY, \"Jan 2025\", ...) is plotted as unordered categories, not a sorted time axis. We\n // don't parse (MM/DD vs DD/MM is ambiguous) — we tell the author to return ISO dates instead.\n if (x && xField?.kind === \"string\" && rows.some((r) => looksLikeLooseDate(r[x]))) {\n notes.push(\n `Column \"${x}\" looks like non-ISO dates; plotted as unordered categories. Return ISO dates (YYYY-MM-DD) for a sorted time axis.`,\n );\n }\n\n let series: SeriesSpec[];\n let yAxisRight: AxisSpec | undefined;\n if (pivotDim && yNames.length === 1) {\n const result = pivotData(rows, x, pivotDim, yNames[0]!);\n rows = result.data;\n series = result.seriesKeys.map((key) => ({ key, label: key }));\n if (result.collapsed > 0)\n notes.push(\n `Summed ${result.collapsed} row(s) that shared the same ${x} + ${pivotDim} — the data looked unaggregated.`,\n );\n // The pivot path can't also carry a right-axis measure; say so instead of dropping it silently.\n if (y2Names.length > 0)\n notes.push(\n `Secondary-axis measure(s) ${y2Names.map((n) => `\"${n}\"`).join(\", \")} were dropped because the chart is split into series by ${pivotDim}.`,\n );\n // Too many series = an indistinguishable color soup. Keep the largest by total, fold the\n // rest into one \"Other\" series (stacking is part-to-whole, so the total must be preserved).\n if (series.length > MAX_SERIES) {\n const grand = (key: string) => rows.reduce((a, r) => a + (Number(r[key]) || 0), 0);\n const drop = [...series].sort((a, b) => grand(b.key) - grand(a.key)).slice(MAX_SERIES - 1);\n const dropKeys = new Set(drop.map((s) => s.key));\n for (const r of rows) {\n let other = 0;\n for (const k of dropKeys) {\n other += Number(r[k]) || 0;\n delete r[k];\n }\n r.Other = other;\n }\n series = [...series.filter((s) => !dropKeys.has(s.key)), { key: \"Other\", label: \"Other\" }];\n notes.push(`Grouped ${drop.length} smaller series into \"Other\".`);\n }\n } else {\n // Left-axis series + (optional) right-axis series for a dual-axis combo.\n // encode.line names left-axis measures to draw as a line instead of bars (same-axis combo).\n const lineNames = encode.line ? (Array.isArray(encode.line) ? encode.line : [encode.line]) : [];\n series = yNames.map((key) => ({\n key,\n label: byName.get(key)?.label ?? key,\n ...(lineNames.includes(key) && { type: \"line\" as const }),\n }));\n // A line measure that wasn't also listed in y is still plotted (as a line), same as y2 —\n // x / y / line each name their own column. Skip unknown columns so a typo draws nothing.\n for (const key of lineNames) {\n if (yNames.includes(key) || y2Names.includes(key) || !byName.has(key)) continue;\n series.push({ key, label: byName.get(key)?.label ?? key, type: \"line\" });\n }\n for (const key of y2Names) series.push({ key, label: byName.get(key)?.label ?? key, axis: \"right\" });\n if (y2Names.length > 0) {\n const f = byName.get(y2Names[0]!);\n yAxisRight = {\n ...(f?.label && { label: f.label }),\n ...(f?.format && { format: f.format }),\n ...(f?.currency && { currency: f.currency }),\n };\n }\n // Sum duplicate x rows: unaggregated SQL (no GROUP BY) yields repeated categories that\n // would otherwise render as overlapping/duplicate bars instead of one summed value.\n if (x && series.length > 0) {\n const agg = aggregateByX(\n rows,\n x,\n series.map((s) => s.key),\n );\n rows = agg.rows;\n if (agg.collapsed > 0) {\n notes.push(`Summed ${agg.collapsed} row(s) that shared the same ${x} — the data looked unaggregated.`);\n // Summing is right for counts, wrong for rates/ratios — flag it so the agent can pre-aggregate.\n const rateCols = series.filter((s) => byName.get(s.key)?.format === \"percent\").map((s) => s.key);\n if (rateCols.length)\n notes.push(\n `${rateCols.map((c) => `\"${c}\"`).join(\", \")} looks like a rate; summing rates is usually wrong — compute SUM(numerator)/SUM(denominator) in SQL instead.`,\n );\n }\n }\n }\n\n // Zero series on a plotting chart type = a blank chart with nothing to draw. The usual cause is\n // a value column that arrived as strings (typed as a dimension) or a fields/encode declaration\n // that left no measure. Note it (or throw under strict) instead of rendering a silent blank.\n if (series.length === 0) {\n const msg =\n \"No measure column to plot - the chart has no data series. Check that value columns contain numbers (not strings), or declare types via fields.\";\n if (opts.strict) throw new Error(msg);\n notes.push(msg);\n }\n\n // Forced-type precondition notes: the shape doesn't fit the requested chart, so we reinterpreted\n // rather than reject. Say what we did (strict throws) so the author isn't left guessing.\n if (opts.chartType === \"pie\" && series.length > 1) {\n const msg = `A pie needs one category + one measure; got ${series.length} measures - showing them as separate slices, which is usually not what a pie means.`;\n if (opts.strict) throw new Error(msg);\n notes.push(msg);\n }\n if (opts.chartType === \"line\" && xField?.kind === \"string\" && series.length > 0) {\n const msg = `A line over categorical \"${x}\" implies an order that may not exist; consider a bar chart.`;\n if (opts.strict) throw new Error(msg);\n notes.push(msg);\n }\n\n // Sort a time / numeric x ascending. Agent SQL often omits ORDER BY, which makes lines\n // zig-zag and numeric bars appear out of order. Category (string) x keeps source order.\n if (x && chartType !== \"pie\") rows = sortRowsByX(rows, x, xField);\n\n // Fill time gaps so lines/areas render breaks correctly.\n if (xField?.role === \"time\" && xField.granularity && x) {\n rows = fillMissingTimeIntervals(\n rows,\n x,\n series.map((s) => s.key),\n xField.granularity,\n );\n }\n\n // Too many categorical bars are unreadable. Keep the top N by value (a bar chart is a ranking,\n // so \"top N\" is the honest reduction) and drop the tail — no \"Other\" bar, which would mislead\n // when the tail is large. Continuous line/area is left alone (dense series are legitimate).\n if (chartType === \"bar\" && series.length > 0 && rows.length > MAX_BARS) {\n const origLen = rows.length;\n const total = (r: Record<string, unknown>) => series.reduce((a, s) => a + (Number(r[s.key]) || 0), 0);\n const keep = new Set([...rows].sort((a, b) => total(b) - total(a)).slice(0, MAX_BARS));\n rows = rows.filter((r) => keep.has(r));\n notes.push(`Showing the top ${rows.length} of ${origLen} categories by value.`);\n }\n\n // Very dense line/area: stride-downsample to bound the payload (keep every Nth point + the\n // last, preserving range and shape). Lines tolerate many points, so the threshold is high.\n if ((chartType === \"line\" || chartType === \"area\") && rows.length > MAX_POINTS) {\n const origLen = rows.length;\n const step = Math.ceil(origLen / MAX_POINTS);\n const sampled = rows.filter((_, i) => i % step === 0);\n if (sampled[sampled.length - 1] !== rows[origLen - 1]) sampled.push(rows[origLen - 1]!);\n rows = sampled;\n notes.push(`Downsampled ${origLen} points to ${rows.length} for display.`);\n }\n\n // Stacked modes: zero-fill missing (x, series) cells so stacks stay aligned (a missing cell\n // is \"0 contribution\", not a hole that shifts the bars above it). Non-stacked keeps the gap.\n if ((opts.stacking === \"stacked\" || opts.stacking === \"stacked100\") && series.length > 0) {\n for (const row of rows) for (const s of series) if (row[s.key] == null) row[s.key] = 0;\n }\n\n // Pie: drop non-positive slices, order largest-first, and fold a long tail into \"Other\".\n // A slice is \"small\" if it ranks beyond MAX_PIE_SLICES OR is under MIN_PIE_FRACTION of the\n // total (this is what catches the sub-1% slivers that make a pie unreadable). Only fold when\n // it collapses >1 slice — a lone small slice is left alone.\n if (chartType === \"pie\" && series.length > 0) {\n const k = series[0]!.key;\n // All values zero/negative: plot magnitudes of the negatives so the pie isn't blank.\n if (!rows.some((r) => Number(r[k]) > 0)) {\n const neg = rows.filter((r) => r[k] != null && Number(r[k]) < 0);\n rows = neg.map((r) => ({ ...r, [k]: Math.abs(Number(r[k])) }));\n if (rows.length > 0) notes.push(\"All values were negative — showing their magnitudes.\");\n } else {\n rows = rows.filter((r) => r[k] != null && Number(r[k]) > 0);\n }\n rows = rows.sort((a, b) => (Number(b[k]) || 0) - (Number(a[k]) || 0));\n const total = rows.reduce((sum, r) => sum + (Number(r[k]) || 0), 0);\n const isSmall = (r: Record<string, unknown>, i: number) =>\n i >= MAX_PIE_SLICES - 1 || (total > 0 && (Number(r[k]) || 0) / total < MIN_PIE_FRACTION);\n const small = rows.filter(isSmall);\n if (small.length > 1) {\n const kept = rows.filter((r, i) => !isSmall(r, i));\n const other = small.reduce((sum, r) => sum + (Number(r[k]) || 0), 0);\n rows = [...kept, { [x]: \"Other\", [k]: other }];\n notes.push(`Grouped ${small.length} small slices into \"Other\".`);\n }\n }\n\n // Axis format descriptors (the spec declares formatting; the widget applies it at render time).\n const firstMeasure = byName.get(yNames[0] ?? \"\");\n const yAxis = {\n ...(series.length === 1 && { label: series[0]!.label }),\n ...(firstMeasure?.format && { format: firstMeasure.format }),\n ...(firstMeasure?.format === \"percent\" && {\n fraction: isFractionScale(\n rows,\n series.filter((s) => s.axis !== \"right\").map((s) => s.key),\n ),\n }),\n ...(firstMeasure?.currency && { currency: firstMeasure.currency }),\n };\n if (yAxisRight?.format === \"percent\") yAxisRight.fraction = isFractionScale(rows, y2Names);\n const xAxis = {\n ...(xField && { label: xField.label }),\n ...(xField?.granularity && { granularity: xField.granularity }),\n // Numeric (non-time) x: signal the renderer to use a linear scale on line/area, so points\n // sit at their true positions (irregular gaps show) instead of evenly-spaced categories.\n ...(xField?.kind === \"number\" && { numeric: true }),\n };\n\n // Orientation follows the x TYPE, not a raw count. Only CATEGORICAL bars flip horizontal (for\n // readable labels when there are many). Time/numeric x stay vertical: time reads left->right and a\n // numeric axis is linear — flipping them is wrong (and was the bug that sent month series sideways).\n const hasComboLine = series.some((s) => s.type === \"line\");\n let horizontal = opts.horizontal;\n if (\n horizontal == null &&\n chartType === \"bar\" &&\n xField?.kind === \"string\" &&\n rows.length > HORIZONTAL_CATEGORY_THRESHOLD\n ) {\n horizontal = true;\n }\n // A same-axis combo line is only meaningful over vertical bars (a connected line on a horizontal\n // layout becomes a meaningless diagonal), so never render a combo horizontal — even if asked.\n if (hasComboLine) horizontal = false;\n\n const columns = buildColumns(x, xField, series, byName, rows);\n\n // Reference lines: an average computed from the primary (left) series, and/or a target value.\n const reference: ReferenceLine[] = [];\n if (opts.reference) {\n const primary = series.find((s) => s.axis !== \"right\") ?? series[0];\n if (opts.reference.average && primary) {\n const vals = rows.map((r) => Number(r[primary.key])).filter((v) => Number.isFinite(v));\n if (vals.length)\n reference.push({\n value: Math.round((vals.reduce((a, b) => a + b, 0) / vals.length) * 100) / 100,\n label: \"Avg\",\n });\n }\n if (opts.reference.target != null) reference.push({ value: opts.reference.target, label: \"Target\" });\n }\n\n return {\n chartType,\n data: rows,\n x,\n series,\n ...(reference.length > 0 && { reference }),\n ...(Object.keys(xAxis).length > 0 && { xAxis }),\n ...(Object.keys(yAxis).length > 0 && { yAxis }),\n ...(yAxisRight && Object.keys(yAxisRight).length > 0 && { yAxisRight }),\n legend: series.length > 1,\n ...(opts.stacking && { stacking: opts.stacking }),\n ...(horizontal != null && { horizontal }),\n ...(opts.title && { title: opts.title }),\n columns,\n ...(notes.length > 0 && { notes }),\n };\n}\n\n// Prepend data-source notes (e.g. a truncation warning) to a spec from a branch that doesn't\n// build its own notes list.\nfunction withNotes(spec: ChartSpec, notes?: string[]): ChartSpec {\n return notes?.length ? { ...spec, notes: [...notes, ...(spec.notes ?? [])] } : spec;\n}\n\n// Scatter/bubble: x and y are measures (one row = one point). No aggregation, pivot, or sort — that\n// would destroy the cloud. Optional size (3rd measure) = bubble; first dimension = the point label.\nfunction resolveScatter(\n rows: Record<string, unknown>[],\n fields: FieldMeta[],\n encode: Encode,\n opts: ResolveOptions,\n): ChartSpec {\n const byName = new Map(fields.map((f) => [f.name, f]));\n const measures = fields.filter((f) => f.role === \"measure\");\n const yEnc = Array.isArray(encode.y) ? encode.y[0] : encode.y;\n const x = encode.x ?? measures[0]?.name;\n const y = yEnc ?? measures.find((m) => m.name !== x)?.name;\n if (!x || !y) {\n throw new Error(\n \"A scatter chart needs two numeric columns. Select at least two measures (x and y), e.g. orders and revenue.\",\n );\n }\n const xField = byName.get(x);\n const yField = byName.get(y);\n const isNum = (f?: FieldMeta) => f?.kind === \"number\";\n if (!isNum(xField) || !isNum(yField)) {\n throw new Error(`A scatter chart needs numeric x and y; \"${x}\" or \"${y}\" is not numeric.`);\n }\n const size = encode.size && byName.get(encode.size)?.kind === \"number\" ? encode.size : undefined;\n\n // encode.series groups points into colored series by a categorical column. A numeric column\n // makes no sense as a point-cloud grouping, so honor it only when it's a category/text field.\n const groupField = encode.series ? byName.get(encode.series) : undefined;\n const groupKey = groupField && groupField.kind !== \"number\" ? groupField.name : undefined;\n const pointLabel = fields.find((f) => f.role === \"dimension\" && f.name !== groupKey)?.name;\n\n // Dense scatters overplot into an unreadable mass. Sample down to a cap (mirrors the line/area\n // MAX_POINTS idiom) so the cloud stays legible; small scatters pass through untouched. Stride\n // sampling preserves the overall shape/extent while thinning uniformly.\n const scatterNotes: string[] = [];\n if (rows.length > MAX_SCATTER_POINTS) {\n const origLen = rows.length;\n const step = Math.ceil(origLen / MAX_SCATTER_POINTS);\n rows = rows.filter((_, i) => i % step === 0);\n scatterNotes.push(`Showing a sample of ${rows.length} of ${origLen} points.`);\n }\n\n const colMeta = (k: string, f = byName.get(k)) => ({\n key: k,\n label: f?.label ?? k,\n ...(f?.format && { format: f.format }),\n ...(f?.currency && { currency: f.currency }),\n });\n const xAxis: AxisSpec = {\n ...(xField?.label && { label: xField.label }),\n ...(xField?.format && { format: xField.format }),\n ...(xField?.currency && { currency: xField.currency }),\n numeric: true,\n };\n const yAxis: AxisSpec = {\n ...(yField?.label && { label: yField.label }),\n ...(yField?.format && { format: yField.format }),\n ...(yField?.currency && { currency: yField.currency }),\n };\n\n if (groupKey) {\n // Keep the largest groups by point count; fold the rest into \"Other\" so a high-cardinality\n // dimension doesn't become an unreadable legend of one-point colors.\n const counts = new Map<string, number>();\n for (const r of rows) {\n const g = String(r[groupKey] ?? \"(No value)\");\n counts.set(g, (counts.get(g) ?? 0) + 1);\n }\n const ordered = [...counts.keys()].sort((a, b) => counts.get(b)! - counts.get(a)!);\n const capped = ordered.length > MAX_SERIES;\n const kept = new Set(capped ? ordered.slice(0, MAX_SERIES - 1) : ordered);\n const groupCols = capped ? [...kept, \"Other\"] : [...kept];\n // Each point's y lands in its own group column (null elsewhere); x and size stay shared, so\n // the widget draws one colored series per group off a single point per row.\n const data = rows.map((r) => {\n const g = String(r[groupKey] ?? \"(No value)\");\n const col = kept.has(g) ? g : \"Other\";\n return {\n [x]: r[x],\n [col]: r[y],\n ...(size && { [size]: r[size] }),\n ...(pointLabel && { [pointLabel]: r[pointLabel] }),\n };\n });\n const notes = [\n ...scatterNotes,\n ...(capped ? [`Grouped ${ordered.length - kept.size} smaller categories into \"Other\".`] : []),\n ];\n return {\n chartType: \"scatter\",\n data,\n x,\n series: groupCols.map((g) => ({ key: g, label: g })),\n ...(size && { size }),\n ...(pointLabel && { pointLabel }),\n xAxis,\n yAxis,\n legend: true,\n ...(opts.title && { title: opts.title }),\n ...(notes.length && { notes }),\n columns: [\n colMeta(x),\n ...groupCols.map((g) => ({\n key: g,\n label: g,\n ...(yField?.format && { format: yField.format }),\n ...(yField?.currency && { currency: yField.currency }),\n })),\n ...(size ? [colMeta(size)] : []),\n ...(pointLabel ? [colMeta(pointLabel)] : []),\n ],\n };\n }\n\n const columns = [x, y, ...(size ? [size] : []), ...(pointLabel ? [pointLabel] : [])].map((k) => colMeta(k));\n return {\n chartType: \"scatter\",\n data: rows,\n x,\n series: [{ key: y, label: yField?.label ?? y }],\n ...(size && { size }),\n ...(pointLabel && { pointLabel }),\n xAxis,\n yAxis,\n legend: false,\n ...(opts.title && { title: opts.title }),\n ...(scatterNotes.length && { notes: scatterNotes }),\n columns,\n };\n}\n\n// Funnel: a stage dimension + a value, drawn as ordered descending segments. No axes, no pivot.\nfunction resolveFunnel(\n rows: Record<string, unknown>[],\n fields: FieldMeta[],\n encode: Encode,\n opts: ResolveOptions,\n): ChartSpec {\n const byName = new Map(fields.map((f) => [f.name, f]));\n const dim = encode.x ?? fields.find((f) => f.role === \"dimension\")?.name;\n const yEnc = Array.isArray(encode.y) ? encode.y[0] : encode.y;\n const value = yEnc ?? fields.find((f) => f.role === \"measure\")?.name;\n if (!dim || !value) {\n throw new Error(\"A funnel chart needs a stage/label column and a numeric value column (e.g. stage + count).\");\n }\n const valField = byName.get(value);\n // A funnel segment must be positive; drop non-positive stages (but never end up empty).\n const positive = rows.filter((r) => (Number(r[value]) || 0) > 0);\n const data = positive.length > 0 ? positive : rows;\n const colMeta = (k: string) => {\n const f = byName.get(k);\n return {\n key: k,\n label: f?.label ?? k,\n ...(f?.format && { format: f.format }),\n ...(f?.currency && { currency: f.currency }),\n };\n };\n // No real category to stage the funnel: the stage column is a promoted measure (its values, not a\n // dimension, became the labels). Say so (strict throws) instead of silently reinterpreting.\n const notes: string[] = [];\n if (!encode.x && byName.get(dim)?.kind === \"number\") {\n const msg = `A funnel needs a stage/label column and one measure; got only measures - using \"${dim}\" values as stage labels.`;\n if (opts.strict) throw new Error(msg);\n notes.push(msg);\n }\n return {\n chartType: \"funnel\",\n data,\n x: dim,\n series: [{ key: value, label: valField?.label ?? value }],\n legend: false,\n ...(opts.title && { title: opts.title }),\n columns: [dim, value].map(colMeta),\n ...(notes.length && { notes }),\n };\n}\n\n// Waterfall / bridge: ordered steps whose value is a SIGNED delta (+ gain, − loss). Start/end\n// \"totals\" anchor from 0; the renderer computes the running cumulative + offsets. Totals are taken\n// from a marker column (values like total/opening/closing) if present, else the first & last rows.\nconst TOTAL_RE = /^\\s*(total|subtotal|opening|closing|start|end|net|balance)\\b/i;\nfunction resolveWaterfall(\n rows: Record<string, unknown>[],\n fields: FieldMeta[],\n encode: Encode,\n opts: ResolveOptions,\n): ChartSpec {\n const byName = new Map(fields.map((f) => [f.name, f]));\n const dim = encode.x ?? fields.find((f) => f.role === \"dimension\")?.name;\n const value =\n (Array.isArray(encode.y) ? encode.y[0] : encode.y) ??\n fields.find((f) => f.role === \"measure\" && f.name !== dim)?.name;\n if (!dim || !value) {\n throw new Error(\n \"A waterfall needs a step label and a numeric value. The value should be the SIGNED change at each step (e.g. -240000 for a loss); mark the start/end rows as totals.\",\n );\n }\n if (rows.length < 2) {\n throw new Error(\"A waterfall needs at least a start total and one change step.\");\n }\n // Totals: a marker column (e.g. kind/type/direction = total/opening/closing) if one exists,\n // otherwise the conventional first and last rows.\n const markerCol = fields.find(\n (f) =>\n f.name !== dim &&\n f.name !== value &&\n f.role === \"dimension\" &&\n rows.some((r) => TOTAL_RE.test(String(r[f.name]))),\n );\n const totals = markerCol\n ? rows.filter((r) => TOTAL_RE.test(String(r[markerCol.name]))).map((r) => String(r[dim]))\n : [String(rows[0]![dim]), String(rows[rows.length - 1]![dim])];\n // Say when we guessed: a pure-delta input has no totals, so defaulting first/last would be wrong.\n const notes = markerCol\n ? []\n : [\n \"No totals column found, so the first and last rows are treated as the opening and closing totals. Mark a column (e.g. type = total) to change this.\",\n ];\n\n const valField = byName.get(value);\n const colMeta = (k: string) => {\n const f = byName.get(k);\n return {\n key: k,\n label: f?.label ?? k,\n ...(f?.format && { format: f.format }),\n ...(f?.currency && { currency: f.currency }),\n };\n };\n return {\n chartType: \"waterfall\",\n data: rows,\n x: dim,\n series: [{ key: value, label: valField?.label ?? value }],\n ...(totals.length > 0 && { totals: [...new Set(totals)] }),\n ...(notes.length && { notes }),\n yAxis: {\n ...(valField?.label && { label: valField.label }),\n ...(valField?.format && { format: valField.format }),\n ...(valField?.currency && { currency: valField.currency }),\n },\n legend: false,\n ...(opts.title && { title: opts.title }),\n columns: [dim, value].map(colMeta),\n };\n}\n\n// Sort rows by x ascending for time / numeric axes (chronological / numeric order). String\n// categories are left in source order, which respects an explicit ORDER BY in the query.\nfunction sortRowsByX(\n rows: Record<string, unknown>[],\n xKey: string,\n xField: FieldMeta | undefined,\n): Record<string, unknown>[] {\n if (xField?.role === \"time\") {\n const t = (v: unknown) => {\n let s = String(v);\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(s)) s += \"T00:00:00.000Z\";\n else if (/^\\d{4}-\\d{2}-\\d{2}T/.test(s) && !/[Z+-]\\d{0,2}:?\\d{0,2}$/.test(s.slice(10))) s += \"Z\";\n const ms = new Date(s).getTime();\n return Number.isNaN(ms) ? Infinity : ms; // unparseable dates sort to the end\n };\n return [...rows].sort((a, b) => t(a[xKey]) - t(b[xKey]));\n }\n if (xField?.kind === \"number\") {\n return [...rows].sort((a, b) => (Number(a[xKey]) || 0) - (Number(b[xKey]) || 0));\n }\n return rows;\n}\n\n// Group rows by x and sum measure columns, folding unaggregated duplicates into one row per x.\n// `collapsed` = how many extra rows were merged (0 when the data was already aggregated).\nfunction aggregateByX(\n rows: Record<string, unknown>[],\n xKey: string,\n measureKeys: string[],\n): { rows: Record<string, unknown>[]; collapsed: number } {\n const groups = new Map<string, Record<string, unknown>>();\n let collapsed = 0;\n for (const row of rows) {\n const key = String(row[xKey]);\n const existing = groups.get(key);\n if (!existing) {\n groups.set(key, { ...row });\n } else {\n collapsed++;\n for (const m of measureKeys) {\n existing[m] = (Number(existing[m]) || 0) + (Number(row[m]) || 0);\n }\n }\n }\n return { rows: Array.from(groups.values()), collapsed };\n}\n\nfunction buildColumns(\n x: string,\n xField: FieldMeta | undefined,\n series: SeriesSpec[],\n byName: Map<string, FieldMeta>,\n rows: Record<string, unknown>[],\n): ColumnSpec[] {\n const cols: ColumnSpec[] = [];\n if (x) {\n cols.push({\n key: x,\n label: xField?.label ?? x,\n ...(xField?.granularity && { granularity: xField.granularity }),\n });\n }\n for (const s of series) {\n const f = byName.get(s.key);\n cols.push({\n key: s.key,\n label: s.label,\n ...(f?.format && { format: f.format }),\n ...(f?.format === \"percent\" && { fraction: isFractionScale(rows, [s.key]) }),\n ...(f?.currency && { currency: f.currency }),\n });\n }\n return cols;\n}\n"],"mappings":"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","names":[]}
@@ -1,3 +1,71 @@
1
+ // src/validate.ts
2
+ var SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "bigint"]);
3
+ var NUMERIC_STRING = /^-?\d+(\.\d+)?$/;
4
+ function isScalar(v) {
5
+ return v == null || SCALAR_TYPES.has(typeof v) || v instanceof Date;
6
+ }
7
+ function isNumericString(v) {
8
+ return typeof v === "string" && NUMERIC_STRING.test(v);
9
+ }
10
+ function isYearLike(v) {
11
+ const s = String(v);
12
+ if (!/^\d{4}$/.test(s)) return false;
13
+ const y = Number(s);
14
+ return y >= 1900 && y <= 2100;
15
+ }
16
+ function allNumericStrings(values) {
17
+ const vals = values.filter((v) => v != null);
18
+ if (vals.length === 0) return false;
19
+ return vals.every(isNumericString) && !vals.every(isYearLike);
20
+ }
21
+ function validateRowsShape(rows) {
22
+ if (!Array.isArray(rows)) {
23
+ throw new Error(
24
+ `Expected the data source to return { rows: [...] } as an array of objects; got ${rows === null ? "null" : typeof rows}.`
25
+ );
26
+ }
27
+ if (rows.length === 0) return;
28
+ const first = rows[0];
29
+ if (first === null || typeof first !== "object" || Array.isArray(first)) {
30
+ throw new Error(
31
+ `Each row must be a flat object keyed by column name (e.g. { region: "EU", revenue: 100 }); got ${Array.isArray(first) ? "an array" : typeof first}. If your driver returns arrays, map each row to an object first.`
32
+ );
33
+ }
34
+ }
35
+ function warnUntypedColumns(data, sample = 50) {
36
+ const declared = new Set((data.fields ?? []).filter((f) => f.kind).map((f) => f.name));
37
+ const rows = data.rows.slice(0, sample);
38
+ const cols = rows[0] ? Object.keys(rows[0]) : [];
39
+ const out = [];
40
+ for (const c of cols) {
41
+ if (declared.has(c)) continue;
42
+ const vals = rows.map((r) => r[c]).filter((v) => v != null);
43
+ if (vals.length === 0) continue;
44
+ if (allNumericStrings(vals)) {
45
+ out.push(
46
+ `Column "${c}" arrived as numbers stored as strings; coerced to numbers so it can be plotted. Declare its kind or return numbers to silence this.`
47
+ );
48
+ } else if (vals.some((v) => typeof v === "object" && !(v instanceof Date))) {
49
+ out.push(`Column "${c}" holds objects (a driver-wrapped value?); normalize to a scalar or declare its fields.`);
50
+ }
51
+ }
52
+ return out;
53
+ }
54
+ function assertPlottedScalar(spec, sample = 50) {
55
+ const keys = [spec.x, ...spec.series.map((s) => s.key)].filter(Boolean);
56
+ for (const row of spec.data.slice(0, sample)) {
57
+ for (const k of keys) {
58
+ const v = row[k];
59
+ if (!isScalar(v)) {
60
+ const kind = Array.isArray(v) ? "an array" : "an object";
61
+ throw new Error(
62
+ `Column "${k}" contains ${kind}, but charts need scalar values. Select a field of it (e.g. ${k}.id) or cast it to text in your query.`
63
+ );
64
+ }
65
+ }
66
+ }
67
+ }
68
+
1
69
  // src/resolve/infer.ts
2
70
  function sniffTimeGranularity(value) {
3
71
  if (typeof value !== "string") return null;
@@ -50,15 +118,17 @@ function sniffKind(rows, name) {
50
118
  return "string";
51
119
  };
52
120
  let kind;
53
- let seen = 0;
121
+ const sample = [];
54
122
  for (const r of rows) {
55
123
  const v = r[name];
56
124
  if (v == null) continue;
125
+ sample.push(v);
57
126
  const k = kindOf(v);
58
127
  if (kind === void 0) kind = k;
59
128
  else if (k !== kind) return "string";
60
- if (++seen >= KIND_SAMPLE) break;
129
+ if (sample.length >= KIND_SAMPLE) break;
61
130
  }
131
+ if (kind === "string" && allNumericStrings(sample)) return "number";
62
132
  return kind ?? "string";
63
133
  }
64
134
  function roleFromKind(kind) {
@@ -82,6 +152,19 @@ function formatHint(name, values) {
82
152
  }
83
153
  return void 0;
84
154
  }
155
+ var LOOSE_DATE_RES = [
156
+ /^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}$/,
157
+ // 01/15/2026, 15-01-2026
158
+ /^\d{1,2}[/-]\d{4}$/,
159
+ // 01/2026
160
+ /^[A-Za-z]{3,9}\.?\s+\d{4}$/,
161
+ // Jan 2025, January 2025
162
+ /^[A-Za-z]{3,9}\.?\s+\d{1,2},?\s+\d{4}$/
163
+ // Jan 15, 2025
164
+ ];
165
+ function looksLikeLooseDate(value) {
166
+ return typeof value === "string" && sniffTimeGranularity(value) === null && LOOSE_DATE_RES.some((re) => re.test(value));
167
+ }
85
168
  function granularityHint(name) {
86
169
  const n = name.toLowerCase();
87
170
  if (n.includes("year")) return "year";
@@ -100,7 +183,10 @@ function inferFields(data) {
100
183
  const kind = d?.kind ?? sniffKind(rows, name);
101
184
  const role = d?.role ?? roleFromKind(kind);
102
185
  const granularity = d?.granularity ?? (kind === "time" ? sniffGranularityFromValues(rows.map((r) => r[name])) ?? granularityHint(name) : void 0);
103
- const format = d?.format ?? (kind === "number" ? formatHint(name, rows.map((r) => r[name])) : void 0);
186
+ const format = d?.format ?? (kind === "number" ? formatHint(
187
+ name,
188
+ rows.map((r) => r[name])
189
+ ) : void 0);
104
190
  return {
105
191
  name,
106
192
  kind,
@@ -153,7 +239,10 @@ function buildChartData({
153
239
  });
154
240
  const fields = columns.map((col) => {
155
241
  const kind = kindByName.get(col.name);
156
- const format = kind === "number" ? formatHint(col.name, out.map((r) => r[col.name])) : void 0;
242
+ const format = kind === "number" ? formatHint(
243
+ col.name,
244
+ out.map((r) => r[col.name])
245
+ ) : void 0;
157
246
  const granularity = kind === "time" ? sniffGranularityFromValues(out.map((r) => r[col.name])) ?? granularityHint(col.name) : void 0;
158
247
  return {
159
248
  name: col.name,
@@ -178,9 +267,13 @@ function assertReadOnlySql(sql) {
178
267
  }
179
268
 
180
269
  export {
270
+ validateRowsShape,
271
+ warnUntypedColumns,
272
+ assertPlottedScalar,
273
+ looksLikeLooseDate,
181
274
  inferFields,
182
275
  defaultNormalizeCell,
183
276
  buildChartData,
184
277
  assertReadOnlySql
185
278
  };
186
- //# sourceMappingURL=chunk-I3HINKHN.js.map
279
+ //# sourceMappingURL=chunk-IXRGLZX2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validate.ts","../src/resolve/infer.ts","../src/adapters/sql.ts"],"sourcesContent":["// Lightweight, fail-loud guards on the developer-returned data. Because the rows ultimately\n// come from an LLM-authored query (so the TS type is a claim the model can break) and every\n// chart renderer below us fails SILENTLY on a bad shape, we turn \"invisible blank chart\" into\n// a clear, agent-correctable error. Cheap: structural check + sampled scalar check on the\n// columns we actually plot. Never scans large result sets; never rejects carried-but-unplotted columns.\nimport type { ChartData, ChartSpec } from \"./types.js\";\n\nconst SCALAR_TYPES = new Set([\"string\", \"number\", \"boolean\", \"bigint\"]);\nconst NUMERIC_STRING = /^-?\\d+(\\.\\d+)?$/;\n\nfunction isScalar(v: unknown): boolean {\n return v == null || SCALAR_TYPES.has(typeof v) || v instanceof Date;\n}\n\n/** A value that is a number stored as a string (e.g. \"1234\", \"-9.5\") — the classic driver footgun. */\nexport function isNumericString(v: unknown): boolean {\n return typeof v === \"string\" && NUMERIC_STRING.test(v);\n}\n\n/** A 4-digit value (as string or number) in a plausible year range — a legitimate string/number\n * ambiguity we must NOT coerce to a measure. Shares the 1900-2100 window with sniffTimeGranularity\n * so recovery, inference, and warnUntypedColumns agree on what \"year-like\" means. */\nexport function isYearLike(v: unknown): boolean {\n const s = String(v);\n if (!/^\\d{4}$/.test(s)) return false;\n const y = Number(s);\n return y >= 1900 && y <= 2100;\n}\n\n/** Non-null sampled values are ALL numeric strings and NONE is year-like: safe to treat as a\n * number column. Returns false for an empty sample. Used by both warnUntypedColumns (advisory)\n * and inference recovery (typing) so behavior matches. */\nexport function allNumericStrings(values: unknown[]): boolean {\n const vals = values.filter((v) => v != null);\n if (vals.length === 0) return false;\n return vals.every(isNumericString) && !vals.every(isYearLike);\n}\n\n/** Generic shape check: the data source must return rows as an array of flat objects. */\nexport function validateRowsShape(rows: unknown): asserts rows is Record<string, unknown>[] {\n if (!Array.isArray(rows)) {\n throw new Error(\n `Expected the data source to return { rows: [...] } as an array of objects; got ${rows === null ? \"null\" : typeof rows}.`,\n );\n }\n if (rows.length === 0) return;\n const first = rows[0];\n if (first === null || typeof first !== \"object\" || Array.isArray(first)) {\n throw new Error(\n `Each row must be a flat object keyed by column name (e.g. { region: \"EU\", revenue: 100 }); ` +\n `got ${Array.isArray(first) ? \"an array\" : typeof first}. ` +\n `If your driver returns arrays, map each row to an object first.`,\n );\n }\n}\n\n// A column typed only by inference (no declared `kind`) whose values are numbers-stored-as-strings\n// or driver wrapper objects is the classic integration footgun: resolve() then charts numbers as\n// categories, or blanks the chart on objects. This is a wiring mistake in the caller's runSql (a\n// SQL driver that string-encodes numerics, or hands back Date/wrapper objects), not bad data, so it\n// warns the developer rather than throwing. Silent when the caller declared `fields` (kinds win).\nexport function warnUntypedColumns(data: ChartData, sample = 50): string[] {\n const declared = new Set((data.fields ?? []).filter((f) => f.kind).map((f) => f.name));\n const rows = data.rows.slice(0, sample);\n const cols = rows[0] ? Object.keys(rows[0]) : [];\n const out: string[] = [];\n for (const c of cols) {\n if (declared.has(c)) continue;\n const vals = rows.map((r) => r[c]).filter((v) => v != null);\n if (vals.length === 0) continue;\n // Skip all-4-digit columns: a bare year is a legitimate string/number ambiguity we must not force.\n if (allNumericStrings(vals)) {\n out.push(\n `Column \"${c}\" arrived as numbers stored as strings; coerced to numbers so it can be plotted. Declare its kind or return numbers to silence this.`,\n );\n } else if (vals.some((v) => typeof v === \"object\" && !(v instanceof Date))) {\n out.push(`Column \"${c}\" holds objects (a driver-wrapped value?); normalize to a scalar or declare its fields.`);\n }\n }\n return out;\n}\n\n/** Precise check: every column we actually plot (x + series) must hold scalar values. Sampled. */\nexport function assertPlottedScalar(spec: ChartSpec, sample = 50): void {\n const keys = [spec.x, ...spec.series.map((s) => s.key)].filter(Boolean);\n for (const row of spec.data.slice(0, sample)) {\n for (const k of keys) {\n const v = row[k];\n if (!isScalar(v)) {\n const kind = Array.isArray(v) ? \"an array\" : \"an object\";\n throw new Error(\n `Column \"${k}\" contains ${kind}, but charts need scalar values. ` +\n `Select a field of it (e.g. ${k}.id) or cast it to text in your query.`,\n );\n }\n }\n }\n}\n","// Typing inference: turn a ChartData into a complete, role-tagged FieldMeta list.\n// Priority: declared fields win; anything missing is sniffed from the row values.\n// This is what lets resolve() decide x / y / series when the data source did not\n// supply full typing (e.g. raw SQL rows).\nimport type { ChartData, FieldFormat, FieldKind, FieldMeta, FieldRole, TimeGranularity } from \"../types.js\";\nimport { allNumericStrings } from \"../validate.js\";\n\n// Period-string -> granularity. Lets the raw-SQL path recognize time buckets that aren't full ISO\n// dates (e.g. strftime('%Y-%m') -> \"2025-01\") so time series are typed TEMPORAL — ordered, drawn\n// vertically, nicely labelled — instead of being mistaken for high-cardinality categories. Used\n// when the data source declares no column types, so the kind is inferred from the value shape.\nexport function sniffTimeGranularity(value: unknown): TimeGranularity | null {\n if (typeof value !== \"string\") return null;\n if (/^\\d{4}-\\d{2}-\\d{2}([T ]|$)/.test(value)) return \"day\"; // 2025-01-15 or a full datetime\n if (/^\\d{4}-\\d{2}$/.test(value)) return \"month\"; // 2025-01\n if (/^\\d{4}-?Q[1-4]$/i.test(value)) return \"quarter\"; // 2025-Q1 / 2025Q1\n if (/^\\d{4}-W\\d{2}$/i.test(value)) return \"week\"; // 2025-W03 (ISO week)\n if (/^\\d{4}$/.test(value)) {\n const y = Number(value); // bare year as a string (\"2025\"); guard the range to avoid 4-digit codes\n if (y >= 1900 && y <= 2100) return \"year\";\n }\n return null;\n}\n\n// Granularity from the actual values. date_trunc('month'|'quarter'|'year'|'week') yields FULL\n// dates, so a single value's shape reads as \"day\" and a monthly series gets ~30x null-filled\n// into disconnected dots. Scan the distinct dates instead: all Jan 1 -> year, all first-of-\n// quarter spaced like quarters -> quarter, all first-of-month -> month, all the same weekday ->\n// week, else day. Period strings (YYYY-MM, YYYY-Qn, ...) state their own granularity; mixed or\n// unparseable values bail out (null).\nconst DAY_MS = 86_400_000;\nexport function sniffGranularityFromValues(values: unknown[]): TimeGranularity | null {\n const seen = new Set<string>();\n const dates: Date[] = [];\n for (const v of values) {\n if (v == null) continue;\n const g = sniffTimeGranularity(v);\n if (g === null) return null;\n if (g !== \"day\") return g;\n const day = String(v).slice(0, 10);\n if (seen.has(day)) continue;\n seen.add(day);\n const d = new Date(`${day}T00:00:00.000Z`);\n if (isNaN(d.getTime())) return null;\n dates.push(d);\n }\n if (dates.length === 0) return null;\n if (dates.length === 1) return \"day\"; // one distinct date: no periodicity to read\n const times = dates.map((d) => d.getTime()).sort((a, b) => a - b);\n let minGap = Infinity;\n for (let i = 1; i < times.length; i++) minGap = Math.min(minGap, times[i]! - times[i - 1]!);\n const all = (p: (d: Date) => boolean) => dates.every(p);\n if (all((d) => d.getUTCDate() === 1)) {\n if (all((d) => d.getUTCMonth() === 0)) return \"year\";\n // First-of-quarter alone also matches monthly data (Jan, Feb, ...); the gap disambiguates.\n if (all((d) => d.getUTCMonth() % 3 === 0) && minGap > 31 * DAY_MS) return \"quarter\";\n return \"month\";\n }\n if (all((d) => d.getUTCDay() === dates[0]!.getUTCDay())) return \"week\";\n return \"day\";\n}\n\n// One value can lie (a text column whose first value is \"2024\" or \"2025-01-15\" would type the\n// whole column time/number), so sample several and require consensus; disagreement -> string.\nconst KIND_SAMPLE = 20;\nexport function sniffKind(rows: Record<string, unknown>[], name: string): FieldKind {\n const kindOf = (v: unknown): FieldKind => {\n if (typeof v === \"number\" || typeof v === \"bigint\") return \"number\";\n if (typeof v === \"boolean\") return \"boolean\";\n if (typeof v === \"string\" && sniffTimeGranularity(v) !== null) return \"time\";\n return \"string\";\n };\n let kind: FieldKind | undefined;\n const sample: unknown[] = [];\n for (const r of rows) {\n const v = r[name];\n if (v == null) continue;\n sample.push(v);\n const k = kindOf(v);\n if (kind === undefined) kind = k;\n else if (k !== kind) return \"string\";\n if (sample.length >= KIND_SAMPLE) break;\n }\n // Numeric-string recovery: a column that consensus-typed as `string` but holds only numeric\n // strings (and isn't year-like) is a driver footgun, not a category. Type it `number` so\n // resolve()'s measure-coercion can plot it, instead of demoting it to a silent blank series.\n if (kind === \"string\" && allNumericStrings(sample)) return \"number\";\n return kind ?? \"string\";\n}\n\nexport function roleFromKind(kind: FieldKind): FieldRole {\n if (kind === \"time\") return \"time\";\n if (kind === \"number\") return \"measure\";\n return \"dimension\";\n}\n\nexport function titleCase(s: string): string {\n return s.replace(/_/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n// Name-based hints — a purely conventional fallback used only when the data source declares no\n// format/granularity. Centralized here so the adapter path (buildChartData) and the raw-rows path\n// (inferFields) apply identical rules.\n\n// A numeric column whose name reads as a percentage renders as a percent. pct/percent/percentage\n// names are unambiguous; rate/ratio names are not (exchange_rate at 1.08, hourly_rate at 45), so\n// they only hold when no value contradicts a 0-1 fraction reading. Currency is deliberately NOT\n// guessed from names (the code is unknowable) — declare it. camelCase is normalized to snake.\nexport function formatHint(name: string, values?: unknown[]): FieldFormat | undefined {\n const n = name.replace(/([a-z0-9])([A-Z])/g, \"$1_$2\").toLowerCase();\n if (/(^|_)(pct|percent|percentage)(_|$)/.test(n)) return \"percent\";\n if (/(^|_)(rate|ratio)(_|$)/.test(n)) {\n for (const v of values ?? []) {\n if (v == null) continue;\n const x = Number(v);\n if (Number.isFinite(x) && Math.abs(x) > 1) return undefined;\n }\n return \"percent\";\n }\n return undefined;\n}\n\n// Non-ISO date shapes we recognize only to WARN about (MM/DD/YYYY, DD-MM-YYYY, \"Jan 2025\", ...).\n// We deliberately do NOT parse them: MM/DD vs DD/MM is ambiguous, so guessing an order would be\n// worse than plotting source-order categories with a note telling the author to return ISO dates.\nconst LOOSE_DATE_RES = [\n /^\\d{1,2}[/-]\\d{1,2}[/-]\\d{2,4}$/, // 01/15/2026, 15-01-2026\n /^\\d{1,2}[/-]\\d{4}$/, // 01/2026\n /^[A-Za-z]{3,9}\\.?\\s+\\d{4}$/, // Jan 2025, January 2025\n /^[A-Za-z]{3,9}\\.?\\s+\\d{1,2},?\\s+\\d{4}$/, // Jan 15, 2025\n];\n\n/** A string value that reads as a date in a non-ISO (ambiguous) format. sniffTimeGranularity\n * already claims the ISO shapes; this only matches the ones we plot as categories + warn about. */\nexport function looksLikeLooseDate(value: unknown): boolean {\n return (\n typeof value === \"string\" && sniffTimeGranularity(value) === null && LOOSE_DATE_RES.some((re) => re.test(value))\n );\n}\n\n// Granularity from the column name, for time columns whose values don't reveal it (e.g. a DATE\n// named \"week_start\" holding full dates). Defaults to day.\nexport function granularityHint(name: string): TimeGranularity {\n const n = name.toLowerCase();\n if (n.includes(\"year\")) return \"year\";\n if (n.includes(\"quarter\")) return \"quarter\";\n if (n.includes(\"month\")) return \"month\";\n if (n.includes(\"week\")) return \"week\";\n return \"day\";\n}\n\n/** Complete field metadata for every column, declared values taking precedence. */\nexport function inferFields(data: ChartData): FieldMeta[] {\n const { rows, fields } = data;\n const declared = new Map((fields ?? []).map((f) => [f.name, f]));\n // Union of returned columns + any declared-only names: declaring fields for SOME columns\n // augments their typing, it does not drop the columns you didn't declare.\n const rowKeys = rows[0] ? Object.keys(rows[0]) : [];\n const names = [...rowKeys, ...[...declared.keys()].filter((n) => !rowKeys.includes(n))];\n\n return names.map((name) => {\n const d = declared.get(name);\n const kind: FieldKind = d?.kind ?? sniffKind(rows, name);\n const role: FieldRole = d?.role ?? roleFromKind(kind);\n // Time columns with no declared granularity: infer it from the values (YYYY-MM -> month,\n // all first-of-month full dates -> month). declared > value-scan > name-hint\n const granularity =\n d?.granularity ??\n (kind === \"time\" ? (sniffGranularityFromValues(rows.map((r) => r[name])) ?? granularityHint(name)) : undefined);\n // declared > name-hint (rate -> percent); only for numeric columns\n const format =\n d?.format ??\n (kind === \"number\"\n ? formatHint(\n name,\n rows.map((r) => r[name]),\n )\n : undefined);\n return {\n name,\n kind,\n role,\n label: d?.label ?? titleCase(name),\n ...(format && { format }),\n ...(granularity && { granularity }),\n ...(d?.additive != null && { additive: d.additive }),\n ...(d?.currency && { currency: d.currency }),\n };\n });\n}\n","// Shared adapter core. An adapter's only engine-specific jobs are (a) report each column's engine\n// type, (b) map that type to a FieldKind, and (c) normalize a cell to a flat scalar. The rest —\n// assembling typed FieldMeta (with name-based format/granularity hints), the ChartData shape, and\n// the read-only check — lives here so every adapter shares it.\n//\n// Connection, auth, read-only enforcement, cost caps and timeouts belong to the caller's database\n// role; assertReadOnlySql is a cheap extra check on LLM-authored SQL, not a substitute for one.\nimport type { ChartData, FieldKind, FieldMeta } from \"../types.js\";\nimport { roleFromKind, titleCase, formatHint, granularityHint, sniffGranularityFromValues } from \"../resolve/infer.js\";\n\n/** A source column: its name + the engine's declared type, in whatever shape the driver reports. */\nexport interface SourceColumn {\n name: string;\n /** Engine type, passed verbatim to mapKind (e.g. \"INT64\", a Postgres OID, \"VARCHAR\", a field object). */\n type: unknown;\n}\n\n/** Map an engine's column type to our FieldKind. The one piece of real per-engine work. */\nexport type KindMapper = (type: unknown, column: SourceColumn) => FieldKind;\n\n/** Coerce one cell value to a flat scalar, guided by the resolved kind. */\nexport type CellNormalizer = (value: unknown, kind: FieldKind, column: SourceColumn) => unknown;\n\nexport interface BuildChartDataOptions {\n rows: Record<string, unknown>[];\n columns: SourceColumn[];\n mapKind: KindMapper;\n /** Optional; defaults to {@link defaultNormalizeCell}, which suits drivers that return JS-native values. */\n normalizeCell?: CellNormalizer;\n}\n\n/** Default cell normalizer: pass scalars through, coerce numbers/booleans, stringify non-scalars.\n * Handles `{ value }` wrapper objects (some drivers wrap typed values). Adapters whose driver\n * needs special handling (e.g. BigQuery's typed-string + wrapper mix) supply their own. */\nexport function defaultNormalizeCell(value: unknown, kind: FieldKind): unknown {\n if (value == null) return null;\n const obj = typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n if (kind === \"number\") {\n const raw = obj ? (\"value\" in obj ? obj.value : (value as { toString(): string }).toString()) : value;\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n }\n if (kind === \"boolean\") {\n if (typeof value === \"boolean\") return value;\n const s = String(obj && \"value\" in obj ? obj.value : value).toLowerCase();\n return s === \"true\" || s === \"1\" ? true : s === \"false\" || s === \"0\" ? false : null;\n }\n if (kind === \"time\") {\n if (obj && \"value\" in obj) return String(obj.value);\n return value instanceof Date ? value.toISOString() : typeof value === \"string\" ? value : String(value);\n }\n // string-ish: unwrap { value }, stringify arrays/objects so the scalar guard passes, else pass through.\n if (obj) {\n if (Array.isArray(value)) return JSON.stringify(value);\n if (\"value\" in obj) return String(obj.value);\n return JSON.stringify(value);\n }\n return value;\n}\n\n/**\n * Turn a SQL result (rows + column types) into typed ChartData. Builds a complete FieldMeta per\n * column from the engine kind + name-based format/granularity hints, then normalizes every cell.\n */\nexport function buildChartData({\n rows,\n columns,\n mapKind,\n normalizeCell = defaultNormalizeCell,\n}: BuildChartDataOptions): ChartData {\n const kindByName = new Map(columns.map((col) => [col.name, mapKind(col.type, col)]));\n const out = rows.map((row) => {\n const r: Record<string, unknown> = {};\n for (const col of columns) r[col.name] = normalizeCell(row[col.name], kindByName.get(col.name)!, col);\n return r;\n });\n const fields: FieldMeta[] = columns.map((col) => {\n const kind = kindByName.get(col.name)!;\n const format =\n kind === \"number\"\n ? formatHint(\n col.name,\n out.map((r) => r[col.name]),\n )\n : undefined;\n // Granularity from the normalized values (all first-of-month -> month); name-hint fallback.\n const granularity =\n kind === \"time\"\n ? (sniffGranularityFromValues(out.map((r) => r[col.name])) ?? granularityHint(col.name))\n : undefined;\n return {\n name: col.name,\n kind,\n role: roleFromKind(kind),\n label: titleCase(col.name),\n ...(format && { format }),\n ...(granularity && { granularity }),\n };\n });\n return { rows: out, fields };\n}\n\n/**\n * Reject anything that isn't a single SELECT/WITH statement. A cheap check on LLM-authored SQL;\n * enforce real read-only access with a database role.\n */\nexport function assertReadOnlySql(sql: string): void {\n const t = sql.trim().replace(/;\\s*$/, \"\");\n if (/;/.test(t)) throw new Error(\"Only a single statement is allowed.\");\n if (!/^(select|with)\\b/i.test(t)) throw new Error(\"Only read-only SELECT queries are allowed.\");\n if (\n /\\b(insert|update|delete|merge|drop|alter|create|truncate|grant|revoke|call|export|load|copy|execute|attach|vacuum|lock)\\b/i.test(\n t,\n )\n ) {\n throw new Error(\"Write/DDL statements are not allowed.\");\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ declare function main(argv: string[]): Promise<void>;
3
+
4
+ export { main };