Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@bonnard/sdk

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bonnard/sdk - npm Package Compare versions

Comparing version
0.4.2
to
0.4.3
+2
dist/ai/index.d.ts
export { createTools } from "./tools.js";
export type { BonnardTool, BonnardClient } from "./types.js";
export { createTools } from "./tools.js";
import { DynamicStructuredTool } from "@langchain/core/tools";
import type { BonnardClient } from "./types.js";
/**
* Create Bonnard tools for LangChain / LangGraph.
*
* Returns a `DynamicStructuredTool[]` so tools can be spread directly:
* ```ts
* import { createTools } from "@bonnard/sdk/ai/langchain"
* const tools = createTools(bonnardClient)
* const agent = createReactAgent({ llm, tools: [...tools, ...myOtherTools] })
* ```
*/
export declare function createTools(client: BonnardClient): DynamicStructuredTool<any, any, unknown, string, string>[];
import { DynamicStructuredTool } from "@langchain/core/tools";
import { createTools as createBonnardTools } from "./tools.js";
/**
* Create Bonnard tools for LangChain / LangGraph.
*
* Returns a `DynamicStructuredTool[]` so tools can be spread directly:
* ```ts
* import { createTools } from "@bonnard/sdk/ai/langchain"
* const tools = createTools(bonnardClient)
* const agent = createReactAgent({ llm, tools: [...tools, ...myOtherTools] })
* ```
*/
export function createTools(client) {
const bonnardTools = createBonnardTools(client);
return bonnardTools.map((t) => new DynamicStructuredTool({
name: t.name,
description: t.description,
schema: t.schema,
func: async (args) => JSON.stringify(await t.execute(args)),
}));
}
import type { BonnardClient, BonnardTool } from "./types.js";
export declare function createTools(client: BonnardClient): BonnardTool[];
import { z } from "zod";
// --- Shared helpers ---
const MAX_ROWS = 250;
function normalizeValue(val) {
if (val === null || val === undefined)
return null;
if (typeof val === "number") {
if (!Number.isInteger(val))
return Math.round(val * 100) / 100;
return val;
}
if (typeof val === "string" && val !== "" && !isNaN(Number(val))) {
const num = Number(val);
if (!Number.isInteger(num))
return Math.round(num * 100) / 100;
return num;
}
return val;
}
function stripPrefixes(rows) {
if (rows.length === 0)
return rows;
const keys = Object.keys(rows[0]);
return rows.map((row) => {
const cleaned = {};
for (const key of keys) {
const shortKey = key.split(".").pop() || key;
cleaned[shortKey] = normalizeValue(row[key]);
}
return cleaned;
});
}
function generateSqlErrorHints(error, sql) {
const hints = [];
if (error.includes("Table or CTE with name") && error.includes("not found")) {
hints.push("- Table not found: use explore_schema to list available views/cubes");
}
if (error.includes("Invalid identifier")) {
hints.push("- Invalid column: check field names via explore_schema with `name` parameter");
hints.push("- In SQL, use column names without view prefix (e.g. `revenue` not `view.revenue`)");
}
if (error.includes("could not be resolved")) {
const hasMeasure = /MEASURE\s*\(/i.test(sql);
const hasGroupBy = /GROUP\s+BY/i.test(sql);
if (!hasMeasure) {
hints.push("- Missing MEASURE(): wrap measure columns in MEASURE() function");
}
if (!hasGroupBy) {
hints.push("- Missing GROUP BY: add GROUP BY for non-aggregated columns");
}
}
if (error.includes("ParserError")) {
hints.push("- SQL syntax error: check for typos or missing keywords");
hints.push("- Use single quotes for string values: WHERE status = 'completed'");
}
if (/\bJOIN\b/i.test(sql)) {
hints.push("- JOINs not supported: use UNION to combine results from different views");
}
if (hints.length === 0) {
hints.push("- Verify table/view names with explore_schema");
hints.push("- Use MEASURE() to aggregate measures");
hints.push("- Include all non-aggregated columns in GROUP BY");
}
return hints.join("\n");
}
// --- Schemas ---
const exploreSchemaSchema = z.object({
name: z.string().optional().describe("Source name to get full details (e.g. 'orders')"),
search: z.string().optional().describe("Keyword to search across all field names and descriptions"),
});
const timeDimensionObject = z.object({
dimension: z.string().describe("Time dimension (e.g. \"orders.created_at\")"),
granularity: z.enum(["day", "week", "month", "quarter", "year"]).optional().describe("Time granularity for grouping"),
dateRange: z.tuple([z.string(), z.string()]).optional().describe("Date range as [start, end] in YYYY-MM-DD format"),
});
const querySchema = z.object({
measures: z.array(z.string()).optional().describe("Measures to query (e.g. [\"orders.revenue\", \"orders.count\"])"),
dimensions: z.array(z.string()).optional().describe("Dimensions to group by (e.g. [\"orders.status\"])"),
timeDimensions: z.array(timeDimensionObject).optional().describe("Time dimensions with date range and optional granularity"),
timeDimension: timeDimensionObject.optional().describe("Alias for timeDimensions (single object)"),
filters: z.array(z.object({
member: z.string().optional().describe("Field to filter (e.g. \"orders.status\")"),
dimension: z.string().optional().describe("Alias for member"),
operator: z.enum(["equals", "notEquals", "contains", "notContains", "gt", "gte", "lt", "lte", "set", "notSet", "inDateRange", "notInDateRange", "beforeDate", "afterDate"]).describe("Filter operator"),
values: z.array(z.string()).optional().describe("Values to filter by (not needed for set/notSet operators)"),
})).optional().describe("Filters to apply"),
segments: z.array(z.string()).optional().describe("Pre-defined filter segments"),
order: z.record(z.enum(["asc", "desc"])).optional().describe("Sort order (e.g. {\"orders.revenue\": \"desc\"})"),
limit: z.number().optional().describe("Maximum rows to return (default: 250, max: 5000)"),
offset: z.number().optional().describe("Number of rows to skip for pagination"),
});
const sqlQuerySchema = z.object({
sql: z.string().describe("SQL query using Cube SQL syntax with MEASURE() for aggregations"),
});
const describeFieldSchema = z.object({
field: z.string().describe("Fully qualified field name (e.g. \"orders.revenue\")"),
});
export function createTools(client) {
const exploreSchema = {
name: "explore_schema",
description: "Discover available data sources (views), their measures, dimensions, and segments. " +
"No arguments returns a summary of all sources. Use 'name' to get full field listings for one source. " +
"Use 'search' to find fields by keyword across all sources.",
schema: exploreSchemaSchema,
execute: async (args) => {
const meta = await client.explore({ viewsOnly: false });
const cubes = meta.cubes;
if (args.search) {
const keyword = args.search.toLowerCase();
const results = [];
const MAX_SEARCH = 50;
for (const cube of cubes) {
if (results.length >= MAX_SEARCH)
break;
const sourceType = cube.type === "view" ? "view" : "cube";
for (const m of cube.measures) {
if (results.length >= MAX_SEARCH)
break;
if (m.name.toLowerCase().includes(keyword) ||
m.description?.toLowerCase().includes(keyword) ||
m.title?.toLowerCase().includes(keyword)) {
results.push({ source: cube.name, sourceType, field: m.name, kind: "measure", type: m.type, description: m.description });
}
}
for (const d of cube.dimensions) {
if (results.length >= MAX_SEARCH)
break;
if (d.name.toLowerCase().includes(keyword) ||
d.description?.toLowerCase().includes(keyword) ||
d.title?.toLowerCase().includes(keyword)) {
results.push({ source: cube.name, sourceType, field: d.name, kind: "dimension", type: d.type, description: d.description });
}
}
for (const s of cube.segments) {
if (results.length >= MAX_SEARCH)
break;
if (s.name.toLowerCase().includes(keyword) ||
s.description?.toLowerCase().includes(keyword) ||
s.title?.toLowerCase().includes(keyword)) {
results.push({ source: cube.name, sourceType, field: s.name, kind: "segment", type: "segment", description: s.description });
}
}
}
return results;
}
if (args.name) {
const cube = cubes.find((c) => c.name === args.name);
if (!cube) {
return { error: `Source '${args.name}' not found. Available sources: ${cubes.map((c) => c.name).join(", ")}` };
}
const dims = cube.dimensions.filter((d) => d.type !== "time");
const timeDims = cube.dimensions.filter((d) => d.type === "time");
return {
name: cube.name,
type: cube.type,
description: cube.description,
measures: cube.measures,
dimensions: dims,
timeDimensions: timeDims,
segments: cube.segments,
};
}
return cubes.map((c) => ({
name: c.name,
type: c.type,
description: c.description,
measures: c.measures.length,
dimensions: c.dimensions.filter((d) => d.type !== "time").length,
timeDimensions: c.dimensions.filter((d) => d.type === "time").length,
segments: c.segments.length,
}));
},
};
const query = {
name: "query",
description: "Query the semantic layer with measures, dimensions, filters, and time dimensions. " +
"All field names must be fully qualified (e.g. \"orders.revenue\"). " +
"Use timeDimensions for date range constraints. Results are capped at 250 rows per response. " +
"If data_completeness is \"partial\", use offset to fetch the next page.",
schema: querySchema,
execute: async (args) => {
// Normalize singular timeDimension → timeDimensions array
const timeDims = args.timeDimensions
|| (args.timeDimension ? [args.timeDimension] : undefined);
// Normalize filter dimension → member
const filters = args.filters?.map((f) => ({
member: f.member || f.dimension,
operator: f.operator,
values: f.values,
})).filter((f) => f.member);
const requestLimit = Math.min(args.limit || MAX_ROWS, 5000);
const cubeQuery = {};
if (args.measures && args.measures.length > 0)
cubeQuery.measures = args.measures;
if (args.dimensions)
cubeQuery.dimensions = args.dimensions;
if (timeDims)
cubeQuery.timeDimensions = timeDims;
if (filters && filters.length > 0)
cubeQuery.filters = filters;
if (args.segments)
cubeQuery.segments = args.segments;
cubeQuery.limit = requestLimit;
if (args.offset)
cubeQuery.offset = args.offset;
if (args.order)
cubeQuery.order = args.order;
const result = await client.rawQuery(cubeQuery);
const data = (result.data || []);
if (data.length === 0)
return { data_completeness: "complete", rows_shown: 0, results: [] };
const capped = data.slice(0, MAX_ROWS);
const isPartial = data.length > MAX_ROWS || data.length >= requestLimit;
const rows = stripPrefixes(capped);
const response = {
data_completeness: isPartial ? "partial" : "complete",
rows_shown: rows.length,
results: rows,
};
if (isPartial) {
const nextOffset = (args.offset || 0) + rows.length;
response.warning = `Partial results — do not sum or average these rows for totals. Use measures for accurate aggregations. To fetch more rows, use offset: ${nextOffset}.`;
}
return response;
},
};
const sqlQuery = {
name: "sql_query",
description: "Execute raw SQL against the semantic layer. Only use when the query tool cannot express what you need " +
"(CTEs, UNIONs, custom arithmetic, CASE expressions). Use MEASURE() for aggregations.",
schema: sqlQuerySchema,
execute: async (args) => {
try {
const result = await client.sql(args.sql);
const data = (result.data || []);
if (data.length === 0)
return { data_completeness: "complete", rows_shown: 0, results: [] };
const capped = data.slice(0, MAX_ROWS);
const isPartial = data.length > MAX_ROWS;
const rows = stripPrefixes(capped);
const response = {
data_completeness: isPartial ? "partial" : "complete",
rows_shown: rows.length,
results: rows,
};
if (isPartial) {
response.warning = `Partial results (${data.length} total). Do not sum or average these rows. Add LIMIT/OFFSET to your SQL to page through results.`;
}
return response;
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
const hints = generateSqlErrorHints(message, args.sql);
return { error: message, hints };
}
},
};
const describeField = {
name: "describe_field",
description: "Get detailed metadata for a specific field including its type, description, and which source it belongs to. " +
"The field name must be fully qualified: \"view_name.field_name\" (e.g. \"orders.revenue\").",
schema: describeFieldSchema,
execute: async (args) => {
const dotIndex = args.field.indexOf(".");
if (dotIndex === -1) {
return { error: "Field must be fully qualified (e.g. \"orders.revenue\")" };
}
const sourceName = args.field.substring(0, dotIndex);
const fieldName = args.field;
const meta = await client.explore({ viewsOnly: false });
const cube = meta.cubes.find((c) => c.name === sourceName);
if (!cube) {
return { error: `Source '${sourceName}' not found. Available sources: ${meta.cubes.map((c) => c.name).join(", ")}` };
}
const sourceType = cube.type === "view" ? "view" : "cube";
const measure = cube.measures.find((m) => m.name === fieldName);
if (measure) {
return {
name: measure.name,
kind: "measure",
type: measure.type,
description: measure.description,
...(measure.format && { format: measure.format }),
...(measure.meta && Object.keys(measure.meta).length > 0 && { meta: measure.meta }),
source: cube.name,
sourceType,
};
}
const dimension = cube.dimensions.find((d) => d.name === fieldName);
if (dimension) {
return {
name: dimension.name,
kind: "dimension",
type: dimension.type,
description: dimension.description,
...(dimension.format && { format: dimension.format }),
...(dimension.meta && Object.keys(dimension.meta).length > 0 && { meta: dimension.meta }),
source: cube.name,
sourceType,
};
}
const segment = cube.segments.find((s) => s.name === fieldName);
if (segment) {
return { name: segment.name, kind: "segment", type: "segment", description: segment.description, source: cube.name, sourceType };
}
return { error: `Field '${fieldName}' not found in source '${sourceName}'` };
},
};
return [exploreSchema, query, sqlQuery, describeField];
}
import type { z } from "zod";
import type { createClient } from "../client.js";
export type BonnardClient = ReturnType<typeof createClient>;
export interface BonnardTool<T = any> {
name: string;
description: string;
schema: z.ZodType<T>;
execute: (args: T) => Promise<unknown>;
}
export {};
import { type CoreTool } from "ai";
import type { BonnardClient } from "./types.js";
/**
* Create Bonnard tools for the Vercel AI SDK.
*
* Returns a `Record<string, CoreTool>` so tools can be spread directly:
* ```ts
* import { createTools } from "@bonnard/sdk/ai/vercel"
* const tools = createTools(bonnardClient)
* const result = await generateText({ model, tools: { ...tools, ...myOtherTools } })
* ```
*/
export declare function createTools(client: BonnardClient): Record<string, CoreTool>;
import { tool } from "ai";
import { createTools as createBonnardTools } from "./tools.js";
/**
* Create Bonnard tools for the Vercel AI SDK.
*
* Returns a `Record<string, CoreTool>` so tools can be spread directly:
* ```ts
* import { createTools } from "@bonnard/sdk/ai/vercel"
* const tools = createTools(bonnardClient)
* const result = await generateText({ model, tools: { ...tools, ...myOtherTools } })
* ```
*/
export function createTools(client) {
const bonnardTools = createBonnardTools(client);
const tools = {};
for (const t of bonnardTools) {
tools[t.name] = tool({
description: t.description,
parameters: t.schema,
execute: t.execute,
});
}
return tools;
}
+1
-1

@@ -1,2 +0,2 @@

"use strict";var Bonnard=(()=>{var y=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var b=(e,r)=>{for(var o in r)y(e,o,{get:r[o],enumerable:!0})},R=(e,r,o,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let a of w(r))!T.call(e,a)&&a!==o&&y(e,a,{get:()=>r[a],enumerable:!(i=f(r,a))||i.enumerable});return e};var g=e=>R(y({},"__esModule",{value:!0}),e);var k={};b(k,{createClient:()=>p,toCubeQuery:()=>c});function c(e){let r={};return e.measures&&(r.measures=e.measures),e.dimensions&&(r.dimensions=e.dimensions),e.filters&&(r.filters=e.filters.map(o=>({member:o.dimension,operator:o.operator,values:o.values}))),e.timeDimension&&(r.timeDimensions=[{dimension:e.timeDimension.dimension,granularity:e.timeDimension.granularity,dateRange:e.timeDimension.dateRange}]),e.orderBy&&(r.order=Object.entries(e.orderBy).map(([o,i])=>[o,i])),e.limit&&(r.limit=e.limit),r}function h(e){try{let r=e.split(".");if(r.length!==3)return 0;let o=r[1].replace(/-/g,"+").replace(/_/g,"/"),i=JSON.parse(atob(o));return typeof i.exp=="number"?i.exp*1e3:0}catch{return 0}}var Q=6e4;function p(e){let r=e.baseUrl||"https://app.bonnard.dev",o=null,i=0;async function a(){if(e.apiKey)return e.apiKey;if(e.fetchToken){let n=Date.now();if(o&&i-Q>n)return o;let t=await e.fetchToken();return o=t,i=h(t),t}throw new Error("BonnardConfig requires either apiKey or fetchToken")}async function l(n,t){let s=await a(),u=await fetch(`${r}${n}`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!u.ok){let d=await u.json().catch(()=>({error:u.statusText}));throw new Error(d.error||"Query failed")}return u.json()}async function m(n){let t=await a(),s=await fetch(`${r}${n}`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});if(!s.ok){let u=await s.json().catch(()=>({error:s.statusText}));throw new Error(u.error||"Request failed")}return s.json()}return{async query(n){let t=c(n),s=await l("/api/cube/query",{query:t});return{data:s.data,annotation:s.annotation}},async rawQuery(n){let t=await l("/api/cube/query",{query:n});return{data:t.data,annotation:t.annotation}},async sql(n){return l("/api/cube/query",{sql:n})},async explore(n){let t=await m("/api/cube/meta");return n?.viewsOnly??!0?{cubes:t.cubes.filter(u=>u.type==="view")}:t},async docs(n){let t=new URLSearchParams;n?.topic?t.set("topic",n.topic):n?.category&&t.set("category",n.category);let s=t.toString();return m(`/api/docs${s?`?${s}`:""}`)}}}return g(k);})();
"use strict";var Bonnard=(()=>{var y=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var h=(e,r)=>{for(var o in r)y(e,o,{get:r[o],enumerable:!0})},b=(e,r,o,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of T(r))!g.call(e,i)&&i!==o&&y(e,i,{get:()=>r[i],enumerable:!(a=w(r,i))||a.enumerable});return e};var R=e=>b(y({},"__esModule",{value:!0}),e);var x={};h(x,{createClient:()=>d,toCubeQuery:()=>c});function c(e){let r={};return e.measures&&(r.measures=e.measures),e.dimensions&&(r.dimensions=e.dimensions),e.filters&&(r.filters=e.filters.map(o=>({member:o.dimension,operator:o.operator,values:o.values}))),e.timeDimension&&(r.timeDimensions=[{dimension:e.timeDimension.dimension,granularity:e.timeDimension.granularity,dateRange:e.timeDimension.dateRange}]),e.orderBy&&(r.order=Object.entries(e.orderBy).map(([o,a])=>[o,a])),e.limit&&(r.limit=e.limit),r}function Q(e){try{let r=e.split(".");if(r.length!==3)return 0;let o=r[1].replace(/-/g,"+").replace(/_/g,"/"),a=JSON.parse(atob(o));return typeof a.exp=="number"?a.exp*1e3:0}catch{return 0}}var q=6e4;function d(e){let r=e.baseUrl||"https://app.bonnard.dev",o=null,a=0,i=null;async function m(){if(e.apiKey)return e.apiKey;if(e.fetchToken){let n=Date.now();return o&&a-q>n?o:i||(i=e.fetchToken().then(t=>(o=t,a=Q(t),t)).finally(()=>{i=null}),i)}throw new Error("BonnardConfig requires either apiKey or fetchToken")}async function l(n,t){let s=await m(),u=await fetch(`${r}${n}`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!u.ok){let f=await u.json().catch(()=>({error:u.statusText}));throw new Error(f.error||"Query failed")}return u.json()}async function p(n){let t=await m(),s=await fetch(`${r}${n}`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});if(!s.ok){let u=await s.json().catch(()=>({error:s.statusText}));throw new Error(u.error||"Request failed")}return s.json()}return{async query(n){let t=c(n),s=await l("/api/cube/query",{query:t});return{data:s.data,annotation:s.annotation}},async rawQuery(n){let t=await l("/api/cube/query",{query:n});return{data:t.data,annotation:t.annotation}},async sql(n){return l("/api/cube/query",{sql:n})},async explore(n){let t=await p("/api/cube/meta");return n?.viewsOnly??!0?{cubes:t.cubes.filter(u=>u.type==="view")}:t},async docs(n){let t=new URLSearchParams;n?.topic?t.set("topic",n.topic):n?.category&&t.set("category",n.category);let s=t.toString();return p(`/api/docs${s?`?${s}`:""}`)}}}return R(x);})();
//# sourceMappingURL=bonnard.iife.js.map
{
"version": 3,
"sources": ["../src/browser.ts", "../src/query.ts", "../src/client.ts"],
"sourcesContent": ["export { createClient } from './client.js';\nexport { toCubeQuery } from './query.js';\n", "/**\n * Bonnard SDK \u2014 Query format conversion (zero IO)\n */\n\nimport type { QueryOptions } from './types.js';\n\n/**\n * Convert SDK QueryOptions into a Cube-native query object.\n * All field names must be fully qualified (e.g. \"orders.revenue\").\n */\nexport function toCubeQuery(options: QueryOptions): Record<string, unknown> {\n const cubeQuery: Record<string, unknown> = {};\n\n if (options.measures) {\n cubeQuery.measures = options.measures;\n }\n\n if (options.dimensions) {\n cubeQuery.dimensions = options.dimensions;\n }\n\n if (options.filters) {\n cubeQuery.filters = options.filters.map(f => ({\n member: f.dimension,\n operator: f.operator,\n values: f.values,\n }));\n }\n\n if (options.timeDimension) {\n cubeQuery.timeDimensions = [{\n dimension: options.timeDimension.dimension,\n granularity: options.timeDimension.granularity,\n dateRange: options.timeDimension.dateRange,\n }];\n }\n\n if (options.orderBy) {\n cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [key, dir]);\n }\n\n if (options.limit) {\n cubeQuery.limit = options.limit;\n }\n\n return cubeQuery;\n}\n", "/**\n * Bonnard SDK \u2014 Client for querying semantic layer\n */\n\nimport type {\n BonnardConfig,\n QueryOptions,\n QueryResult,\n SqlResult,\n CubeQuery,\n ExploreMeta,\n ExploreOptions,\n DocsOptions,\n DocsTopicListResult,\n DocsTopicResult,\n} from './types.js';\nimport { toCubeQuery } from './query.js';\n\n/**\n * Parse JWT expiry from the payload (base64url-decoded middle segment).\n * Returns the `exp` claim as a millisecond timestamp, or 0 if unparseable.\n */\nfunction parseJwtExpiry(token: string): number {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return 0;\n // base64url \u2192 base64 \u2192 decode\n const payload = parts[1]!.replace(/-/g, '+').replace(/_/g, '/');\n const json = JSON.parse(atob(payload));\n return typeof json.exp === 'number' ? json.exp * 1000 : 0;\n } catch {\n return 0;\n }\n}\n\nconst REFRESH_BUFFER_MS = 60_000; // refresh 60s before expiry\n\nexport function createClient(config: BonnardConfig) {\n const baseUrl = config.baseUrl || 'https://app.bonnard.dev';\n\n // Token cache for fetchToken mode\n let cachedToken: string | null = null;\n let cachedExpiry = 0;\n\n async function getToken(): Promise<string> {\n // Static API key \u2014 return directly\n if (config.apiKey) {\n return config.apiKey;\n }\n\n // Token callback \u2014 cache and refresh\n if (config.fetchToken) {\n const now = Date.now();\n if (cachedToken && cachedExpiry - REFRESH_BUFFER_MS > now) {\n return cachedToken;\n }\n\n const token = await config.fetchToken();\n cachedToken = token;\n cachedExpiry = parseJwtExpiry(token);\n return token;\n }\n\n throw new Error('BonnardConfig requires either apiKey or fetchToken');\n }\n\n async function request<T>(endpoint: string, body: unknown): Promise<T> {\n const token = await getToken();\n const res = await fetch(`${baseUrl}${endpoint}`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const error = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error(error.error || 'Query failed');\n }\n\n return res.json();\n }\n\n async function requestGet<T>(endpoint: string): Promise<T> {\n const token = await getToken();\n const res = await fetch(`${baseUrl}${endpoint}`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n },\n });\n\n if (!res.ok) {\n const error = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error(error.error || 'Request failed');\n }\n\n return res.json();\n }\n\n return {\n /**\n * Execute a JSON query against the semantic layer.\n * All field names must be fully qualified (e.g. \"orders.revenue\").\n */\n async query<T = Record<string, unknown>>(options: QueryOptions): Promise<QueryResult<T>> {\n const cubeQuery = toCubeQuery(options);\n\n const result = await request<{ data: T[]; annotation?: QueryResult['annotation'] }>(\n '/api/cube/query',\n { query: cubeQuery }\n );\n\n return { data: result.data, annotation: result.annotation };\n },\n\n /**\n * Execute a raw Cube-native JSON query against the semantic layer.\n * Use this when you already have a Cube API query object.\n */\n async rawQuery<T = Record<string, unknown>>(cubeQuery: CubeQuery): Promise<QueryResult<T>> {\n const result = await request<{ data: T[]; annotation?: QueryResult['annotation'] }>(\n '/api/cube/query',\n { query: cubeQuery }\n );\n\n return { data: result.data, annotation: result.annotation };\n },\n\n /**\n * Execute a SQL query against the semantic layer\n */\n async sql<T = Record<string, unknown>>(query: string): Promise<SqlResult<T>> {\n return request<SqlResult<T>>('/api/cube/query', { sql: query });\n },\n\n /**\n * Discover available cubes, measures, dimensions, and segments.\n * By default returns only views (viewsOnly: true).\n */\n async explore(options?: ExploreOptions): Promise<ExploreMeta> {\n const meta = await requestGet<{ cubes: ExploreMeta['cubes'] }>('/api/cube/meta');\n const viewsOnly = options?.viewsOnly ?? true;\n\n if (viewsOnly) {\n return { cubes: meta.cubes.filter(c => c.type === 'view') };\n }\n\n return meta;\n },\n\n /**\n * Access Bonnard documentation.\n * - `docs()` \u2014 list all topics\n * - `docs({ category: 'dashboards' })` \u2014 list topics in a category\n * - `docs({ topic: 'dashboards.components' })` \u2014 get full markdown content\n */\n async docs(options?: DocsOptions): Promise<DocsTopicListResult | DocsTopicResult> {\n const params = new URLSearchParams();\n if (options?.topic) params.set('topic', options.topic);\n else if (options?.category) params.set('category', options.category);\n const qs = params.toString();\n return requestGet(`/api/docs${qs ? `?${qs}` : ''}`);\n },\n };\n}\n"],
"mappings": "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",
"names": ["browser_exports", "__export", "createClient", "toCubeQuery", "toCubeQuery", "options", "cubeQuery", "f", "key", "dir", "parseJwtExpiry", "token", "parts", "payload", "json", "REFRESH_BUFFER_MS", "createClient", "config", "baseUrl", "cachedToken", "cachedExpiry", "getToken", "now", "request", "endpoint", "body", "res", "error", "requestGet", "options", "cubeQuery", "toCubeQuery", "result", "query", "meta", "c", "params", "qs"]
"sourcesContent": ["export { createClient } from './client.js';\nexport { toCubeQuery } from './query.js';\n", "/**\n * Bonnard SDK \u2014 Query format conversion (zero IO)\n */\n\nimport type { QueryOptions } from './types.js';\n\n/**\n * Convert SDK QueryOptions into a Cube-native query object.\n * All field names must be fully qualified (e.g. \"orders.revenue\").\n */\nexport function toCubeQuery(options: QueryOptions): Record<string, unknown> {\n const cubeQuery: Record<string, unknown> = {};\n\n if (options.measures) {\n cubeQuery.measures = options.measures;\n }\n\n if (options.dimensions) {\n cubeQuery.dimensions = options.dimensions;\n }\n\n if (options.filters) {\n cubeQuery.filters = options.filters.map(f => ({\n member: f.dimension,\n operator: f.operator,\n values: f.values,\n }));\n }\n\n if (options.timeDimension) {\n cubeQuery.timeDimensions = [{\n dimension: options.timeDimension.dimension,\n granularity: options.timeDimension.granularity,\n dateRange: options.timeDimension.dateRange,\n }];\n }\n\n if (options.orderBy) {\n cubeQuery.order = Object.entries(options.orderBy).map(([key, dir]) => [key, dir]);\n }\n\n if (options.limit) {\n cubeQuery.limit = options.limit;\n }\n\n return cubeQuery;\n}\n", "/**\n * Bonnard SDK \u2014 Client for querying semantic layer\n */\n\nimport type {\n BonnardConfig,\n QueryOptions,\n QueryResult,\n SqlResult,\n CubeQuery,\n ExploreMeta,\n ExploreOptions,\n DocsOptions,\n DocsTopicListResult,\n DocsTopicResult,\n} from './types.js';\nimport { toCubeQuery } from './query.js';\n\n/**\n * Parse JWT expiry from the payload (base64url-decoded middle segment).\n * Returns the `exp` claim as a millisecond timestamp, or 0 if unparseable.\n */\nfunction parseJwtExpiry(token: string): number {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return 0;\n // base64url \u2192 base64 \u2192 decode\n const payload = parts[1]!.replace(/-/g, '+').replace(/_/g, '/');\n const json = JSON.parse(atob(payload));\n return typeof json.exp === 'number' ? json.exp * 1000 : 0;\n } catch {\n return 0;\n }\n}\n\nconst REFRESH_BUFFER_MS = 60_000; // refresh 60s before expiry\n\n/**\n * Create a Bonnard client.\n *\n * When `fetchToken` is provided, the returned token is cached automatically\n * and refreshed 60 seconds before its JWT `exp` claim. Concurrent calls are\n * deduplicated so your callback is never invoked more than once at a time.\n */\nexport function createClient(config: BonnardConfig) {\n const baseUrl = config.baseUrl || 'https://app.bonnard.dev';\n\n // Token cache for fetchToken mode\n let cachedToken: string | null = null;\n let cachedExpiry = 0;\n let pendingFetch: Promise<string> | null = null;\n\n async function getToken(): Promise<string> {\n // Static API key \u2014 return directly\n if (config.apiKey) {\n return config.apiKey;\n }\n\n // Token callback \u2014 cache, refresh, and deduplicate\n if (config.fetchToken) {\n const now = Date.now();\n if (cachedToken && cachedExpiry - REFRESH_BUFFER_MS > now) {\n return cachedToken;\n }\n\n // Deduplicate concurrent calls \u2014 share a single in-flight promise\n if (pendingFetch) return pendingFetch;\n\n pendingFetch = config.fetchToken()\n .then((token) => {\n cachedToken = token;\n cachedExpiry = parseJwtExpiry(token);\n return token;\n })\n .finally(() => {\n pendingFetch = null;\n });\n\n return pendingFetch;\n }\n\n throw new Error('BonnardConfig requires either apiKey or fetchToken');\n }\n\n async function request<T>(endpoint: string, body: unknown): Promise<T> {\n const token = await getToken();\n const res = await fetch(`${baseUrl}${endpoint}`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const error = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error(error.error || 'Query failed');\n }\n\n return res.json();\n }\n\n async function requestGet<T>(endpoint: string): Promise<T> {\n const token = await getToken();\n const res = await fetch(`${baseUrl}${endpoint}`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${token}`,\n },\n });\n\n if (!res.ok) {\n const error = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error(error.error || 'Request failed');\n }\n\n return res.json();\n }\n\n return {\n /**\n * Execute a JSON query against the semantic layer.\n * All field names must be fully qualified (e.g. \"orders.revenue\").\n */\n async query<T = Record<string, unknown>>(options: QueryOptions): Promise<QueryResult<T>> {\n const cubeQuery = toCubeQuery(options);\n\n const result = await request<{ data: T[]; annotation?: QueryResult['annotation'] }>(\n '/api/cube/query',\n { query: cubeQuery }\n );\n\n return { data: result.data, annotation: result.annotation };\n },\n\n /**\n * Execute a raw Cube-native JSON query against the semantic layer.\n * Use this when you already have a Cube API query object.\n */\n async rawQuery<T = Record<string, unknown>>(cubeQuery: CubeQuery): Promise<QueryResult<T>> {\n const result = await request<{ data: T[]; annotation?: QueryResult['annotation'] }>(\n '/api/cube/query',\n { query: cubeQuery }\n );\n\n return { data: result.data, annotation: result.annotation };\n },\n\n /**\n * Execute a SQL query against the semantic layer\n */\n async sql<T = Record<string, unknown>>(query: string): Promise<SqlResult<T>> {\n return request<SqlResult<T>>('/api/cube/query', { sql: query });\n },\n\n /**\n * Discover available cubes, measures, dimensions, and segments.\n * By default returns only views (viewsOnly: true).\n */\n async explore(options?: ExploreOptions): Promise<ExploreMeta> {\n const meta = await requestGet<{ cubes: ExploreMeta['cubes'] }>('/api/cube/meta');\n const viewsOnly = options?.viewsOnly ?? true;\n\n if (viewsOnly) {\n return { cubes: meta.cubes.filter(c => c.type === 'view') };\n }\n\n return meta;\n },\n\n /**\n * Access Bonnard documentation.\n * - `docs()` \u2014 list all topics\n * - `docs({ category: 'dashboards' })` \u2014 list topics in a category\n * - `docs({ topic: 'dashboards.components' })` \u2014 get full markdown content\n */\n async docs(options?: DocsOptions): Promise<DocsTopicListResult | DocsTopicResult> {\n const params = new URLSearchParams();\n if (options?.topic) params.set('topic', options.topic);\n else if (options?.category) params.set('category', options.category);\n const qs = params.toString();\n return requestGet(`/api/docs${qs ? `?${qs}` : ''}`);\n },\n };\n}\n"],
"mappings": "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",
"names": ["browser_exports", "__export", "createClient", "toCubeQuery", "toCubeQuery", "options", "cubeQuery", "f", "key", "dir", "parseJwtExpiry", "token", "parts", "payload", "json", "REFRESH_BUFFER_MS", "createClient", "config", "baseUrl", "cachedToken", "cachedExpiry", "pendingFetch", "getToken", "now", "request", "endpoint", "body", "res", "error", "requestGet", "options", "cubeQuery", "toCubeQuery", "result", "query", "meta", "c", "params", "qs"]
}

@@ -5,2 +5,9 @@ /**

import type { BonnardConfig, QueryOptions, QueryResult, SqlResult, CubeQuery, ExploreMeta, ExploreOptions, DocsOptions, DocsTopicListResult, DocsTopicResult } from './types.js';
/**
* Create a Bonnard client.
*
* When `fetchToken` is provided, the returned token is cached automatically
* and refreshed 60 seconds before its JWT `exp` claim. Concurrent calls are
* deduplicated so your callback is never invoked more than once at a time.
*/
export declare function createClient(config: BonnardConfig): {

@@ -7,0 +14,0 @@ /**

@@ -24,2 +24,9 @@ /**

const REFRESH_BUFFER_MS = 60_000; // refresh 60s before expiry
/**
* Create a Bonnard client.
*
* When `fetchToken` is provided, the returned token is cached automatically
* and refreshed 60 seconds before its JWT `exp` claim. Concurrent calls are
* deduplicated so your callback is never invoked more than once at a time.
*/
export function createClient(config) {

@@ -30,2 +37,3 @@ const baseUrl = config.baseUrl || 'https://app.bonnard.dev';

let cachedExpiry = 0;
let pendingFetch = null;
async function getToken() {

@@ -36,3 +44,3 @@ // Static API key — return directly

}
// Token callback — cache and refresh
// Token callback — cache, refresh, and deduplicate
if (config.fetchToken) {

@@ -43,6 +51,15 @@ const now = Date.now();

}
const token = await config.fetchToken();
cachedToken = token;
cachedExpiry = parseJwtExpiry(token);
return token;
// Deduplicate concurrent calls — share a single in-flight promise
if (pendingFetch)
return pendingFetch;
pendingFetch = config.fetchToken()
.then((token) => {
cachedToken = token;
cachedExpiry = parseJwtExpiry(token);
return token;
})
.finally(() => {
pendingFetch = null;
});
return pendingFetch;
}

@@ -49,0 +66,0 @@ throw new Error('BonnardConfig requires either apiKey or fetchToken');

@@ -5,4 +5,16 @@ /**

export interface BonnardConfig {
/** Publishable API key (`bon_pk_…`). Mutually exclusive with `fetchToken`. */
apiKey?: string;
/**
* Callback that returns a short-lived JWT from your server.
*
* The SDK **automatically caches** the token and re-calls this function
* 60 seconds before the JWT's `exp` claim. Concurrent calls while a fetch
* is in-flight are deduplicated — you do **not** need to cache the token
* yourself.
*
* Mutually exclusive with `apiKey`.
*/
fetchToken?: () => Promise<string>;
/** API base URL. Defaults to `https://app.bonnard.dev`. */
baseUrl?: string;

@@ -84,2 +96,4 @@ }

type: string;
format?: string;
meta?: Record<string, unknown>;
}

@@ -86,0 +100,0 @@ export interface CubeSegmentMeta {

{
"name": "@bonnard/sdk",
"version": "0.4.2",
"version": "0.4.3",
"description": "Bonnard SDK - query your semantic layer from any JavaScript or TypeScript app",

@@ -13,2 +13,14 @@ "type": "module",

},
"./ai": {
"import": "./dist/ai/index.js",
"types": "./dist/ai/index.d.ts"
},
"./ai/vercel": {
"import": "./dist/ai/vercel.js",
"types": "./dist/ai/vercel.d.ts"
},
"./ai/langchain": {
"import": "./dist/ai/langchain.js",
"types": "./dist/ai/langchain.d.ts"
},
"./browser": {

@@ -28,5 +40,24 @@ "default": "./dist/bonnard.iife.js"

"dependencies": {},
"peerDependencies": {
"zod": "^3.0.0",
"ai": "^4.0.0",
"@langchain/core": "^1.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
},
"ai": {
"optional": true
},
"@langchain/core": {
"optional": true
}
},
"devDependencies": {
"esbuild": "^0.24.0",
"typescript": "^5.3.3"
"esbuild": "^0.27.3",
"typescript": "^5.3.3",
"zod": "^3.24.0",
"ai": "^4.0.0",
"@langchain/core": "^1.0.0"
},

@@ -33,0 +64,0 @@ "keywords": [