drizzle-cube
Version:
Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.
309 lines (308 loc) • 8.14 kB
JavaScript
import { jsx as R } from "react/jsx-runtime";
import { useState as w, useCallback as m, useEffect as j, useContext as T, createContext as U, useMemo as E } from "react";
class D {
apiUrl;
headers;
constructor(t, r = {}) {
this.apiUrl = r.apiUrl || "/cubejs-api/v1", this.headers = {
"Content-Type": "application/json",
...r.headers
}, t && (this.headers.Authorization = t);
}
async load(t) {
const r = JSON.stringify(t), e = encodeURIComponent(r), a = `${this.apiUrl}/load?query=${e}`, s = await fetch(a, {
method: "GET",
headers: {
// Remove Content-Type for GET request
...Object.fromEntries(
Object.entries(this.headers).filter(([c]) => c !== "Content-Type")
)
},
credentials: "include"
// Include cookies for session auth
});
if (!s.ok) {
let c = `Cube query failed: ${s.status}`;
try {
const u = await s.text();
try {
const l = JSON.parse(u);
l.error ? c = l.error : c += ` ${u}`;
} catch {
c += ` ${u}`;
}
} catch {
}
throw new Error(c);
}
const o = await s.json();
return new C(o);
}
async meta() {
const t = `${this.apiUrl}/meta`, r = await fetch(t, {
method: "GET",
headers: this.headers,
credentials: "include"
});
if (!r.ok)
throw new Error(`Failed to fetch meta: ${r.status}`);
return r.json();
}
async sql(t) {
const r = encodeURIComponent(JSON.stringify(t)), e = `${this.apiUrl}/sql?query=${r}`, a = await fetch(e, {
method: "GET",
headers: {
// Remove Content-Type for GET request
...Object.fromEntries(
Object.entries(this.headers).filter(([s]) => s !== "Content-Type")
)
},
credentials: "include"
});
if (!a.ok)
throw new Error(`SQL generation failed: ${a.status}`);
return a.json();
}
async dryRun(t) {
const r = `${this.apiUrl}/dry-run`, e = await fetch(r, {
method: "POST",
headers: this.headers,
credentials: "include",
body: JSON.stringify({ query: t })
});
if (!e.ok) {
let a = `Dry run failed: ${e.status}`;
try {
const s = await e.text();
try {
const o = JSON.parse(s);
o.error ? a = o.error : a += ` ${s}`;
} catch {
a += ` ${s}`;
}
} catch {
}
throw new Error(a);
}
return e.json();
}
/**
* Execute multiple queries in a single batch request
* Used by BatchCoordinator to optimize network requests
*/
async batchLoad(t) {
const r = `${this.apiUrl}/batch`, e = await fetch(r, {
method: "POST",
headers: this.headers,
credentials: "include",
body: JSON.stringify({ queries: t })
});
if (!e.ok) {
let s = `Batch query failed: ${e.status}`;
try {
const o = await e.text();
try {
const c = JSON.parse(o);
c.error ? s = c.error : s += ` ${o}`;
} catch {
s += ` ${o}`;
}
} catch {
}
throw new Error(s);
}
return (await e.json()).results.map((s) => !s.success && s.error ? {
...new C({ data: [], annotation: {} }),
error: s.error
} : new C(s));
}
}
class C {
loadResponse;
constructor(t) {
this.loadResponse = t;
}
rawData() {
return this.loadResponse.results && this.loadResponse.results[0] ? this.loadResponse.results[0].data || [] : this.loadResponse.data || [];
}
tablePivot() {
return this.rawData();
}
series() {
return this.rawData();
}
annotation() {
return this.loadResponse.results && this.loadResponse.results[0] ? this.loadResponse.results[0].annotation || {} : this.loadResponse.annotation || {};
}
}
function L(n, t = {}) {
return new D(n, t);
}
const P = 900 * 1e3;
let h = null;
function k(n) {
const t = {};
return n.cubes.forEach((r) => {
r.measures.forEach((e) => {
t[e.name] = e.title || e.shortTitle || e.name;
}), r.dimensions.forEach((e) => {
t[e.name] = e.title || e.shortTitle || e.name;
}), r.segments.forEach((e) => {
t[e.name] = e.title || e.shortTitle || e.name;
});
}), t;
}
function v() {
return h ? Date.now() - h.timestamp < P : !1;
}
function F(n) {
const [t, r] = w(null), [e, a] = w({}), [s, o] = w(!0), [c, u] = w(null), l = m(async () => {
if (v() && h) {
r(h.data), a(h.labelMap), o(!1), u(null);
return;
}
try {
o(!0), u(null);
const i = await n.meta(), p = k(i);
h = {
data: i,
labelMap: p,
timestamp: Date.now()
}, r(i), a(p);
} catch (i) {
const p = i instanceof Error ? i.message : "Failed to fetch metadata";
u(p), console.error("Failed to fetch cube metadata:", i);
} finally {
o(!1);
}
}, [n]);
j(() => {
l();
}, [l]);
const d = m((i) => e[i] || i, [e]), f = m(() => {
h = null, l();
}, [l]);
return {
meta: t,
labelMap: e,
loading: s,
error: c,
refetch: f,
getFieldLabel: d
};
}
class J {
queue = [];
flushScheduled = !1;
batchExecutor;
delayMs;
constructor(t, r = 100) {
this.batchExecutor = t, this.delayMs = r;
}
/**
* Register a query to be batched. Returns a promise that resolves
* when the batch is executed and this specific query's result is available.
*/
register(t) {
return new Promise((r, e) => {
this.queue.push({ query: t, resolve: r, reject: e }), this.flushScheduled || this.scheduleFlush();
});
}
/**
* Schedule a flush after a short delay to collect multiple queries.
* The delay allows queries from lazy-loaded portlets that become visible
* during the same scroll action to be batched together.
*/
scheduleFlush() {
this.flushScheduled = !0, setTimeout(() => {
this.flush();
}, this.delayMs);
}
/**
* Execute all queued queries as a batch and resolve individual promises
*/
async flush() {
this.flushScheduled = !1;
const t = this.queue.slice();
if (this.queue = [], t.length !== 0)
try {
const r = t.map((a) => a.query), e = await this.batchExecutor(r);
t.forEach((a, s) => {
const o = e[s];
o && "error" in o && o.error ? a.reject(new Error(o.error)) : a.resolve(o);
});
} catch (r) {
t.forEach((e) => {
e.reject(r instanceof Error ? r : new Error(String(r)));
});
}
}
/**
* Get current queue size (useful for debugging)
*/
getQueueSize() {
return this.queue.length;
}
/**
* Clear the queue (useful for testing/cleanup)
*/
clear() {
this.queue = [], this.flushScheduled = !1;
}
}
const g = U(null);
function G({
cubeApi: n,
apiOptions: t,
token: r,
options: e = {},
features: a = { enableAI: !0, aiEndpoint: "/api/ai/generate" },
// Default to AI enabled for backward compatibility
enableBatching: s = !0,
// Default to batching enabled
batchDelayMs: o = 100,
// Default 100ms batch window
children: c
}) {
const [u, l] = w(null), d = u || {
apiOptions: t || { apiUrl: "/cubejs-api/v1" },
token: r
}, f = E(() => n && !t && !r ? n : L(d.token, d.apiOptions), [n, t, r, d.apiOptions, d.token]), i = E(() => {
if (!s)
return null;
const y = (b) => f.batchLoad(b);
return new J(y, o);
}, [s, f, o]), { meta: p, labelMap: M, loading: S, error: x, getFieldLabel: $, refetch: q } = F(f), O = {
cubeApi: f,
options: e,
meta: p,
labelMap: M,
metaLoading: S,
metaError: x,
getFieldLabel: $,
refetchMeta: q,
updateApiConfig: (y, b) => {
l({
apiOptions: y,
token: b
});
},
features: a,
batchCoordinator: i,
enableBatching: s
};
return /* @__PURE__ */ R(g.Provider, { value: O, children: c });
}
function Q() {
const n = T(g);
if (!n)
throw new Error("useCubeContext must be used within a CubeProvider");
return n;
}
export {
G as C,
F as a,
L as c,
Q as u
};
//# sourceMappingURL=providers-DONuYrGH.js.map