@vibedash/client 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/duckdb-singleton.js +15 -0
- package/dist/provider.d.ts +4 -2
- package/dist/provider.js +25 -4
- package/dist/types.d.ts +6 -2
- package/dist/use-query.js +3 -3
- package/package.json +1 -1
package/dist/duckdb-singleton.js
CHANGED
|
@@ -30,8 +30,23 @@ async function initDuckDB() {
|
|
|
30
30
|
async loadParquet(name, url, forceReload = false) {
|
|
31
31
|
if (loadedTables.has(name) && !forceReload)
|
|
32
32
|
return;
|
|
33
|
+
// Validate URL origin — only allow same-origin or *.vibedash.xyz
|
|
34
|
+
const parsed = new URL(url, window.location.origin);
|
|
35
|
+
const isSameOrigin = parsed.origin === window.location.origin;
|
|
36
|
+
const isVibedash = parsed.hostname.endsWith(".vibedash.xyz");
|
|
37
|
+
if (!isSameOrigin && !isVibedash) {
|
|
38
|
+
throw new Error("Parquet URL origin not allowed");
|
|
39
|
+
}
|
|
33
40
|
// Fetch the Parquet file and register it
|
|
34
41
|
const response = await fetch(url);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`Failed to fetch Parquet: ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
// Reject files larger than 100MB to prevent browser OOM
|
|
46
|
+
const contentLength = response.headers.get("Content-Length");
|
|
47
|
+
if (contentLength && parseInt(contentLength, 10) > 100 * 1024 * 1024) {
|
|
48
|
+
throw new Error("Parquet file too large (>100MB)");
|
|
49
|
+
}
|
|
35
50
|
const buffer = await response.arrayBuffer();
|
|
36
51
|
await db.registerFileBuffer(`${name}.parquet`, new Uint8Array(buffer));
|
|
37
52
|
// Drop existing table on force reload so we pick up the new data
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { VibeDashConfig } from "./types";
|
|
2
2
|
type RefetchFn = () => Promise<void>;
|
|
3
|
-
interface VibeDashContextValue extends VibeDashConfig {
|
|
3
|
+
interface VibeDashContextValue extends Omit<VibeDashConfig, "apiUrl"> {
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
useCredentials: boolean;
|
|
4
6
|
registerQuery: (name: string, fn: RefetchFn, cachedAt: Date | null) => void;
|
|
5
7
|
unregisterQuery: (name: string) => void;
|
|
6
8
|
refreshAll: () => Promise<void>;
|
|
7
9
|
}
|
|
8
|
-
export declare function VibeDashProvider({ apiUrl, projectSlug, dashboardSlug, apiKey, children, }: VibeDashConfig & {
|
|
10
|
+
export declare function VibeDashProvider({ apiUrl: apiUrlProp, projectSlug, dashboardSlug, apiKey, children, }: VibeDashConfig & {
|
|
9
11
|
children: React.ReactNode;
|
|
10
12
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
export declare function useVibeDash(): VibeDashContextValue;
|
package/dist/provider.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useCallback, useRef, useEffect, useState, } from "react";
|
|
3
|
+
const HUB_API_URL = "https://vibedash.xyz/api";
|
|
4
|
+
function resolveApiConfig(apiUrlProp, apiKey) {
|
|
5
|
+
// Explicit override — backward compat for existing dashboards
|
|
6
|
+
if (apiUrlProp) {
|
|
7
|
+
return { apiUrl: apiUrlProp, useCredentials: !apiKey };
|
|
8
|
+
}
|
|
9
|
+
// SSR guard
|
|
10
|
+
if (typeof window === "undefined") {
|
|
11
|
+
return { apiUrl: HUB_API_URL, useCredentials: false };
|
|
12
|
+
}
|
|
13
|
+
const hostname = window.location.hostname;
|
|
14
|
+
const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1";
|
|
15
|
+
if (isLocalhost) {
|
|
16
|
+
// Local dev — Hub API directly with Bearer token
|
|
17
|
+
return { apiUrl: HUB_API_URL, useCredentials: false };
|
|
18
|
+
}
|
|
19
|
+
// Production (*.vibedash.xyz or custom domain) — Worker proxy
|
|
20
|
+
return { apiUrl: "/_api", useCredentials: true };
|
|
21
|
+
}
|
|
3
22
|
const VibeDashContext = createContext(null);
|
|
4
23
|
const POLL_INTERVAL_ACTIVE = 30000; // 30s when caching is on
|
|
5
24
|
const POLL_INTERVAL_IDLE = 300000; // 5min when caching is off
|
|
6
|
-
export function VibeDashProvider({ apiUrl, projectSlug, dashboardSlug, apiKey, children, }) {
|
|
25
|
+
export function VibeDashProvider({ apiUrl: apiUrlProp, projectSlug, dashboardSlug, apiKey, children, }) {
|
|
26
|
+
const { apiUrl, useCredentials } = resolveApiConfig(apiUrlProp, apiKey);
|
|
7
27
|
const queryMap = useRef(new Map());
|
|
8
28
|
const [pollInterval, setPollInterval] = useState(POLL_INTERVAL_IDLE);
|
|
9
29
|
const registerQuery = useCallback((name, fn, cachedAt) => {
|
|
@@ -35,7 +55,7 @@ export function VibeDashProvider({ apiUrl, projectSlug, dashboardSlug, apiKey, c
|
|
|
35
55
|
const res = await fetch(`${apiUrl}/query/status`, {
|
|
36
56
|
method: "POST",
|
|
37
57
|
headers,
|
|
38
|
-
credentials:
|
|
58
|
+
credentials: useCredentials ? "include" : "omit",
|
|
39
59
|
body: JSON.stringify({
|
|
40
60
|
project_slug: projectSlug,
|
|
41
61
|
dashboard_slug: dashboardSlug,
|
|
@@ -78,12 +98,13 @@ export function VibeDashProvider({ apiUrl, projectSlug, dashboardSlug, apiKey, c
|
|
|
78
98
|
cancelled = true;
|
|
79
99
|
clearTimeout(timeoutId);
|
|
80
100
|
};
|
|
81
|
-
}, [apiUrl, projectSlug, dashboardSlug, apiKey, pollInterval]);
|
|
101
|
+
}, [apiUrl, projectSlug, dashboardSlug, apiKey, useCredentials, pollInterval]);
|
|
82
102
|
return (_jsx(VibeDashContext.Provider, { value: {
|
|
83
103
|
apiUrl,
|
|
84
104
|
projectSlug,
|
|
85
105
|
dashboardSlug,
|
|
86
106
|
apiKey,
|
|
107
|
+
useCredentials,
|
|
87
108
|
registerQuery,
|
|
88
109
|
unregisterQuery,
|
|
89
110
|
refreshAll,
|
|
@@ -93,7 +114,7 @@ export function useVibeDash() {
|
|
|
93
114
|
const ctx = useContext(VibeDashContext);
|
|
94
115
|
if (!ctx) {
|
|
95
116
|
throw new Error("useVibeDash must be used within a <VibeDashProvider>. " +
|
|
96
|
-
'Wrap your app in <VibeDashProvider
|
|
117
|
+
'Wrap your app in <VibeDashProvider projectSlug="...">.');
|
|
97
118
|
}
|
|
98
119
|
return ctx;
|
|
99
120
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface VibeDashConfig {
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* API URL override. If omitted, auto-detected:
|
|
4
|
+
* - *.vibedash.xyz or custom domain → "/_api" (Worker proxy)
|
|
5
|
+
* - localhost / 127.0.0.1 → "https://vibedash.xyz/api" (direct)
|
|
6
|
+
*/
|
|
7
|
+
apiUrl?: string;
|
|
4
8
|
/** The project slug (e.g. "data-man") */
|
|
5
9
|
projectSlug: string;
|
|
6
10
|
/** The dashboard slug — enables auto-polling for live cache updates */
|
package/dist/use-query.js
CHANGED
|
@@ -26,7 +26,7 @@ import { useVibeDash } from "./provider";
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export function useQuery(queryName, options) {
|
|
29
|
-
const { apiUrl, projectSlug, apiKey, registerQuery, unregisterQuery } = useVibeDash();
|
|
29
|
+
const { apiUrl, projectSlug, apiKey, useCredentials, registerQuery, unregisterQuery } = useVibeDash();
|
|
30
30
|
const [data, setData] = useState(null);
|
|
31
31
|
const [loading, setLoading] = useState(false);
|
|
32
32
|
const [error, setError] = useState(null);
|
|
@@ -48,7 +48,7 @@ export function useQuery(queryName, options) {
|
|
|
48
48
|
const res = await fetch(`${apiUrl}/query`, {
|
|
49
49
|
method: "POST",
|
|
50
50
|
headers,
|
|
51
|
-
credentials:
|
|
51
|
+
credentials: useCredentials ? "include" : "omit",
|
|
52
52
|
body: JSON.stringify({
|
|
53
53
|
project_slug: projectSlug,
|
|
54
54
|
query_name: queryName,
|
|
@@ -92,7 +92,7 @@ export function useQuery(queryName, options) {
|
|
|
92
92
|
finally {
|
|
93
93
|
setLoading(false);
|
|
94
94
|
}
|
|
95
|
-
}, [apiUrl, projectSlug, queryName, apiKey]);
|
|
95
|
+
}, [apiUrl, projectSlug, queryName, apiKey, useCredentials]);
|
|
96
96
|
// Register this query by name so the provider can:
|
|
97
97
|
// 1. Call refetch via useRefreshAll()
|
|
98
98
|
// 2. Detect stale data via auto-polling and trigger refetch
|