@graphenedata/cli 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -0
- package/dist/cli/bigQuery-OQUNH3VT.js +75 -0
- package/dist/cli/bigQuery-OQUNH3VT.js.map +7 -0
- package/dist/cli/chunk-56K2FF57.js +53 -0
- package/dist/cli/chunk-56K2FF57.js.map +7 -0
- package/dist/cli/chunk-TZTTALAV.js +12868 -0
- package/dist/cli/chunk-TZTTALAV.js.map +7 -0
- package/dist/cli/cli.js +260 -11196
- package/dist/cli/clickhouse-S3BJSKND.js +65 -0
- package/dist/cli/clickhouse-S3BJSKND.js.map +7 -0
- package/dist/cli/duckdb-TKVMONRK.js +87 -0
- package/dist/cli/duckdb-TKVMONRK.js.map +7 -0
- package/dist/cli/serve2-S2LL4D4D.js +448 -0
- package/dist/cli/serve2-S2LL4D4D.js.map +7 -0
- package/dist/cli/snowflake-3VPDEYYP.js +128 -0
- package/dist/cli/snowflake-3VPDEYYP.js.map +7 -0
- package/dist/index.d.ts +63 -0
- package/dist/lang/index.d.ts +63 -0
- package/dist/skills/graphene/SKILL.md +156 -95
- package/dist/skills/graphene/references/big-value.md +6 -41
- package/dist/skills/graphene/references/date-range.md +64 -0
- package/dist/skills/graphene/references/dropdown.md +3 -4
- package/dist/skills/graphene/references/echarts.md +162 -0
- package/dist/skills/graphene/references/gsql.md +55 -25
- package/dist/skills/graphene/references/model-gsql.md +70 -0
- package/dist/skills/graphene/references/table.md +13 -14
- package/dist/skills/graphene/references/text-input.md +2 -1
- package/dist/ui/app.css +239 -340
- package/dist/ui/component-utilities/dataShaping.ts +484 -0
- package/dist/ui/component-utilities/dataSummary.ts +57 -0
- package/dist/ui/component-utilities/enrich.ts +793 -0
- package/dist/ui/component-utilities/format.ts +177 -0
- package/dist/ui/component-utilities/inputUtils.ts +44 -8
- package/dist/ui/component-utilities/theme.ts +200 -0
- package/dist/ui/component-utilities/themeStores.ts +21 -8
- package/dist/ui/component-utilities/types.ts +70 -0
- package/dist/ui/components/AreaChart.svelte +57 -105
- package/dist/ui/components/BarChart.svelte +71 -129
- package/dist/ui/components/BigValue.svelte +24 -40
- package/dist/ui/components/Column.svelte +10 -18
- package/dist/ui/components/DateRange.svelte +54 -21
- package/dist/ui/components/Dropdown.svelte +47 -26
- package/dist/ui/components/DropdownOption.svelte +1 -2
- package/dist/ui/components/ECharts.svelte +181 -67
- package/dist/ui/components/InlineDelta.svelte +50 -31
- package/dist/ui/components/LineChart.svelte +54 -125
- package/dist/ui/components/PieChart.svelte +27 -37
- package/dist/ui/components/QueryLoad.svelte +77 -45
- package/dist/ui/components/Row.svelte +2 -1
- package/dist/ui/components/ScatterPlot.svelte +52 -0
- package/dist/ui/components/Skeleton.svelte +32 -0
- package/dist/ui/components/Table.svelte +3 -2
- package/dist/ui/components/TableGroupRow.svelte +28 -36
- package/dist/ui/components/TableHarness.svelte +32 -0
- package/dist/ui/components/TableHeader.svelte +34 -59
- package/dist/ui/components/TableRow.svelte +14 -38
- package/dist/ui/components/TableSubtotalRow.svelte +18 -21
- package/dist/ui/components/TableTotalRow.svelte +27 -37
- package/dist/ui/components/TextInput.svelte +13 -12
- package/dist/ui/components/Value.svelte +25 -0
- package/dist/ui/components/_Table.svelte +72 -70
- package/dist/ui/internal/ChartGallery.svelte +527 -0
- package/dist/ui/internal/ErrorDisplay.svelte +22 -97
- package/dist/ui/internal/LocalApp.svelte +84 -19
- package/dist/ui/internal/PageNavGroup.svelte +269 -0
- package/dist/ui/internal/Sidebar.svelte +178 -0
- package/dist/ui/internal/SidebarToggle.svelte +47 -0
- package/dist/ui/internal/StyleGallery.svelte +244 -0
- package/dist/ui/internal/clientCache.ts +2 -2
- package/dist/ui/internal/pageInputs.svelte.js +292 -0
- package/dist/ui/internal/queryEngine.ts +112 -129
- package/dist/ui/internal/runSocket.ts +31 -14
- package/dist/ui/internal/sidebar.svelte.js +18 -0
- package/dist/ui/internal/telemetry.ts +51 -16
- package/dist/ui/internal/types.d.ts +7 -0
- package/dist/ui/web.js +30 -11
- package/package.json +40 -38
- package/dist/skills/graphene/references/area-chart.md +0 -95
- package/dist/skills/graphene/references/bar-chart.md +0 -112
- package/dist/skills/graphene/references/line-chart.md +0 -108
- package/dist/skills/graphene/references/pie-chart.md +0 -29
- package/dist/skills/graphene/references/value-formats.md +0 -104
- package/dist/ui/component-utilities/autoFormatting.js +0 -280
- package/dist/ui/component-utilities/builtInFormats.js +0 -481
- package/dist/ui/component-utilities/chartContext.js +0 -12
- package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
- package/dist/ui/component-utilities/checkInputs.js +0 -84
- package/dist/ui/component-utilities/convert.js +0 -15
- package/dist/ui/component-utilities/dateParsing.js +0 -56
- package/dist/ui/component-utilities/dropdownContext.ts +0 -1
- package/dist/ui/component-utilities/echarts.js +0 -252
- package/dist/ui/component-utilities/echartsThemes.js +0 -443
- package/dist/ui/component-utilities/formatTitle.js +0 -24
- package/dist/ui/component-utilities/formatting.js +0 -241
- package/dist/ui/component-utilities/getColumnExtents.js +0 -79
- package/dist/ui/component-utilities/getColumnSummary.js +0 -62
- package/dist/ui/component-utilities/getCompletedData.js +0 -122
- package/dist/ui/component-utilities/getDistinctCount.js +0 -7
- package/dist/ui/component-utilities/getDistinctValues.js +0 -15
- package/dist/ui/component-utilities/getSeriesConfig.js +0 -231
- package/dist/ui/component-utilities/getSortedData.js +0 -9
- package/dist/ui/component-utilities/getStackPercentages.js +0 -45
- package/dist/ui/component-utilities/getStackedData.js +0 -19
- package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
- package/dist/ui/component-utilities/globalContexts.js +0 -1
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
- package/dist/ui/component-utilities/replaceNulls.js +0 -16
- package/dist/ui/component-utilities/tableUtils.ts +0 -107
- package/dist/ui/component-utilities/tidyWithTypes.js +0 -9
- package/dist/ui/components/Area.svelte +0 -214
- package/dist/ui/components/Bar.svelte +0 -347
- package/dist/ui/components/Chart.svelte +0 -995
- package/dist/ui/components/Line.svelte +0 -227
- package/dist/ui/internal/NavSidebar.svelte +0 -396
- package/dist/ui/internal/theme.ts +0 -60
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<br/>
|
|
3
|
+
<a href="https://graphenedata.com">
|
|
4
|
+
<img height="125" alt="Graphene Logo" src="./assets/logo.png" />
|
|
5
|
+
</a>
|
|
6
|
+
<br/>
|
|
7
|
+
<br/>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<b>Graphene</b> is a data analytics framework built for agents.
|
|
12
|
+
<br/>
|
|
13
|
+
Ask questions and build visualizations 10x faster when agents do the work.
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<div align="center">
|
|
17
|
+
<a href="https://graphenedata.com">Website</a>
|
|
18
|
+
•
|
|
19
|
+
<a href="https://github.com/graphene-data/example-flights">Demo Project</a>
|
|
20
|
+
•
|
|
21
|
+
<a href="/docs/setup.md">Setup</a>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<br/>
|
|
25
|
+
|
|
26
|
+
## Why Graphene?
|
|
27
|
+
|
|
28
|
+
Graphene is an everything-as-code analytics framework for SQL-based data exploration, visualization, and reporting. It is designed with coding agents in mind as the primary user persona.
|
|
29
|
+
|
|
30
|
+
It provides two critical pieces that allow coding agents to do better data work:
|
|
31
|
+
|
|
32
|
+
1. **A semantic layer**, which yields more accurate queries. GSQL combines the power of SQL with the governance of metrics and modeled joins.
|
|
33
|
+
2. **A dashboard file type**, which yields more consistent and polished visuals compared to raw Python or Javascript.
|
|
34
|
+
|
|
35
|
+
**Design goals**
|
|
36
|
+
|
|
37
|
+
- Token efficiency. Languages are designed to be brief with minimal boilerplate.
|
|
38
|
+
- Agent ergonomics. Graphene is controlled entirely via CLI. All documentation is inside our agent skill.
|
|
39
|
+
- High ceilings. GSQL follows ANSI and supports over 170 functions; Graphene's visualizations support anything that can be expressed with ECharts.
|
|
40
|
+
|
|
41
|
+
### Versus traditional BI
|
|
42
|
+
|
|
43
|
+
We believe coding agents coupled with an everything-as-code analytics stack beats traditional BI in several ways:
|
|
44
|
+
|
|
45
|
+
- Broad ecosystem of SOTA LLMs, harnesses, skills, and tools
|
|
46
|
+
- Leverage business-wide context from other tools or repos
|
|
47
|
+
- Perform end-to-end tasks across tools, where analytics is just one step
|
|
48
|
+
- More graceful change management and bulk refactors
|
|
49
|
+
- Easily promote/demote logic into or out of the semantic layer
|
|
50
|
+
- Version control and CI. Revert agent mistakes. Run tests on mission-critical dashboards.
|
|
51
|
+
- Tight, complete iteration loops. Agents can validate before running, view dashboards, and iterate locally
|
|
52
|
+
- Leverage continuous agents for self-healing codebases
|
|
53
|
+
|
|
54
|
+
### Open, forever
|
|
55
|
+
|
|
56
|
+
Graphene is free to use, forever. Your business logic lives in your repo and is never locked into a contract with us.
|
|
57
|
+
|
|
58
|
+
### Rich visualizations
|
|
59
|
+
|
|
60
|
+
Graphene pages support visualizations, input components for filtering and dynamic behaviors, and layout modes for monitoring-oriented dashboards vs. narrative-oriented notebooks.
|
|
61
|
+
|
|
62
|
+
<img alt="Graphene Screenshots" src="./assets/page_examples.png"/>
|
|
63
|
+
|
|
64
|
+
### Powerful, next-generation semantic layer
|
|
65
|
+
|
|
66
|
+
Traditional semantic layers give you governance at the expense of capability. They tend to expose niche query APIs that agents aren't familiar with.
|
|
67
|
+
|
|
68
|
+
GSQL's goal is to bring governance _without_ sacrificing capability. It behaves like regular SQL—with CTEs, subqueries, window functions, set operators, and more—but also adds in the concepts of measures and modeled joins from semantic layers.
|
|
69
|
+
|
|
70
|
+
GSQL is inspired by [Malloy](https://github.com/malloydata/malloy), from the creator of LookML Lloyd Tabb, but implements it as good old SQL for agent familiarity.
|
|
71
|
+
|
|
72
|
+
## Get started
|
|
73
|
+
|
|
74
|
+
- [Try the demo project](https://github.com/graphene-data/example-flights)
|
|
75
|
+
- [Create a new Graphene project](/docs/setup.md)
|
|
76
|
+
|
|
77
|
+
Graphene currently supports Snowflake, BigQuery, ClickHouse, and local data (via DuckDB) as data sources. It is easy for us to add more - just ask.
|
|
78
|
+
|
|
79
|
+
Once your project is set up, simply start the dev server via `npm exec graphene serve` (or `pnpm graphene serve`, etc. based on your package manager) and then prompt your coding agent to do analytics work: answer a data question, build a dashboard, edit the model, etc.
|
|
80
|
+
|
|
81
|
+
## How it works
|
|
82
|
+
|
|
83
|
+
Graphene itself is a CLI which can be installed via npm (or pnpm, yarn, etc.). The CLI can run and compile GSQL queries, render pages in the browser, check syntax, print screenshots, and more.
|
|
84
|
+
|
|
85
|
+
A Graphene project can either be a standalone repo or a directory within a larger codebase (such as dbt). It is comprised of _semantic models_ via .gsql files and _pages_ via .md files.
|
|
86
|
+
|
|
87
|
+
### GSQL and Graphene markdown
|
|
88
|
+
|
|
89
|
+
Semantic models are defined like so:
|
|
90
|
+
|
|
91
|
+
```sql
|
|
92
|
+
table orders (
|
|
93
|
+
id BIGINT
|
|
94
|
+
user_id BIGINT
|
|
95
|
+
amount FLOAT
|
|
96
|
+
status STRING
|
|
97
|
+
|
|
98
|
+
join one users on user_id = users.id -- many orders per user
|
|
99
|
+
|
|
100
|
+
is_complete: status = 'Complete' -- dimension (scalar expression)
|
|
101
|
+
revenue: sum(amount) -- measure (agg expression)
|
|
102
|
+
aov: revenue / count(*) -- measures can compose
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
table users (
|
|
106
|
+
id BIGINT
|
|
107
|
+
name VARCHAR
|
|
108
|
+
|
|
109
|
+
join many orders on id = orders.user_id
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Models are then queried via `select`, either directly via CLI or inside a Graphene markdown page like this.
|
|
114
|
+
|
|
115
|
+
````md
|
|
116
|
+
```sql top_customers
|
|
117
|
+
select
|
|
118
|
+
users.name as name, -- Use the dot operator to traverse the modeled join relationship
|
|
119
|
+
revenue -- Invokes the measure
|
|
120
|
+
from orders -- A join statement here is not needed
|
|
121
|
+
group by 1
|
|
122
|
+
order by 2 desc
|
|
123
|
+
limit 10
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
<BigValue data="orders" value="revenue" />
|
|
127
|
+
<BarChart data="top_customers" x="name" y="revenue" />
|
|
128
|
+
````
|
|
129
|
+
|
|
130
|
+
## Documentation
|
|
131
|
+
|
|
132
|
+
Graphene's entire documentation ships as an agent skill in the Graphene npm package. The source files are available [here](/docs).
|
|
133
|
+
|
|
134
|
+
## FAQ
|
|
135
|
+
|
|
136
|
+
<details><summary><b>Why coding agents?</b></summary>
|
|
137
|
+
<br/>
|
|
138
|
+
Context, mostly. If your BI stack lives in a folder right next to the rest of your company’s data and code, an agent can make smarter decisions on what it should be analyzing and why it matters.
|
|
139
|
+
<br/><br/>
|
|
140
|
+
The reverse is also true. If you’re working on building out some new feature or a recommendation for a new client, the agent doing that work can ground it’s approach in real data and past analytical insights.
|
|
141
|
+
<br/><br/>
|
|
142
|
+
Lock-in is the other big reason we hear. Folks post-SaaS are wary of having their data and dashboards locked away in some proprietary tool, or forced to use a single LLM provider. With Graphene, you own all the files in your repo. You can use whatever agent or LLM you’d like.
|
|
143
|
+
</details>
|
|
144
|
+
|
|
145
|
+
<details><summary><b>How do you make money?</b></summary>
|
|
146
|
+
<br/>
|
|
147
|
+
We’re building out Graphene Cloud as a turnkey solution to host the dashboards and reports your agent builds. We also host an MCP server and Slack bot that you can use for quick questions. If you'd like to pilot it, contact us <a href="https://graphenedata.com/contact-us">here</a>.
|
|
148
|
+
</details>
|
|
149
|
+
|
|
150
|
+
<details><summary><b>So does everyone have to use git and a coding agent to use Graphene for BI?</b></summary>
|
|
151
|
+
<br/>
|
|
152
|
+
If you just want to use this project and nothing more, yes. Our managed service, Graphene Cloud, offers a Slack agent, MCP server, and browser-based SaaS experience.
|
|
153
|
+
</details>
|
|
154
|
+
|
|
155
|
+
<details><summary><b>Is my data team out of a job?</b></summary>
|
|
156
|
+
<br/>
|
|
157
|
+
No, but we think the nature of the work is going to change. Instead of manually building out reports, data experts are going to be shaping the skills, models, and tools that the rest of their team uses to answer data questions.
|
|
158
|
+
<br/><br/>
|
|
159
|
+
Data teams are going to be focused on guiding agents on how to approach the trickiest and most nebulous data questions at a company. Questions that still require a data expert’s taste to get a good solution.
|
|
160
|
+
</details>
|
|
161
|
+
|
|
162
|
+
<details><summary><b>Can’t I just vibe code dashboards?</b></summary>
|
|
163
|
+
<br/>
|
|
164
|
+
You could! In fact a lot of the folks we’ve talked to have started down this route. The main problem you’ll run into is consistency. The look and feel of your dashboards and reports are all over the place, and in the worst case they end up using different formula to compute the same key metric.
|
|
165
|
+
<br/><br/>
|
|
166
|
+
GSQL codifies metrics into deterministic objects you can directly invoke in queries. Not only does this ensure that every use of "EBITDA" will be the same, the metadata tags we attach can hint our charts into formatting data correctly.
|
|
167
|
+
<br/><br/>
|
|
168
|
+
Graphene raises the floor so that pages you generate with the help of an agent look beautiful by default, so you can move faster with less tokens.
|
|
169
|
+
</details>
|
|
170
|
+
|
|
171
|
+
<details><summary><b>What software license does this use?</b></summary>
|
|
172
|
+
<br/>
|
|
173
|
+
Graphene is licensed under the Elastic License 2.0 which allows you to use it for internal use cases for free, forever. If you would like to build your own commercial application with Graphene, please contact us <a href="https://graphenedata.com/contact-us">here</a>.
|
|
174
|
+
</details>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
config
|
|
3
|
+
} from "./chunk-56K2FF57.js";
|
|
4
|
+
|
|
5
|
+
// connections/bigQuery.ts
|
|
6
|
+
import { BigQuery, BigQueryDate, BigQueryTimestamp } from "@google-cloud/bigquery";
|
|
7
|
+
function validateBigQueryIdent(ident) {
|
|
8
|
+
if (!/^[\w.-]+$/.test(ident)) throw new Error(`Invalid BigQuery identifier: ${ident}`);
|
|
9
|
+
}
|
|
10
|
+
var BigQueryConnection = class {
|
|
11
|
+
client;
|
|
12
|
+
projectId;
|
|
13
|
+
defaultNamespace;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
options.projectId ||= config.bigquery?.projectId;
|
|
16
|
+
if (!options.projectId) throw new Error("projectId must be set in config or provided in service account credentials");
|
|
17
|
+
this.projectId = options.projectId;
|
|
18
|
+
this.client = new BigQuery({ ...options, userAgent: "Graphene" });
|
|
19
|
+
this.defaultNamespace = config.defaultNamespace;
|
|
20
|
+
}
|
|
21
|
+
async runQuery(sql, params) {
|
|
22
|
+
let [job] = await this.client.createQueryJob({ query: sql, useLegacySql: false, params });
|
|
23
|
+
let [rows] = await job.getQueryResults({ maxResults: 1e4 });
|
|
24
|
+
let metadata = job.metadata || (await job.getMetadata())[0];
|
|
25
|
+
let totalRows = Number(metadata?.statistics?.query?.totalRows ?? rows.length);
|
|
26
|
+
rows.forEach((r) => {
|
|
27
|
+
Object.entries(r).forEach(([k, v]) => {
|
|
28
|
+
if (v instanceof BigQueryTimestamp) r[k] = v.value;
|
|
29
|
+
if (v instanceof BigQueryDate) r[k] = v.value;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return { rows, totalRows };
|
|
33
|
+
}
|
|
34
|
+
async listDatasets() {
|
|
35
|
+
let [datasets] = await this.client.getDatasets();
|
|
36
|
+
return datasets.map((d) => String(d.id || d.metadata.datasetReference?.datasetId || "").toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
async listTables(dataset) {
|
|
39
|
+
if (!dataset) throw new Error("BigQuery requires a dataset");
|
|
40
|
+
validateBigQueryIdent(dataset);
|
|
41
|
+
let resolvedDataset = await this.resolveDatasetName(dataset);
|
|
42
|
+
let res = await this.runQuery(`select table_name as table_name
|
|
43
|
+
from \`${resolvedDataset}.INFORMATION_SCHEMA.TABLES\`
|
|
44
|
+
where table_type in ('BASE TABLE', 'VIEW') order by table_name`);
|
|
45
|
+
return res.rows.map((r) => `${resolvedDataset.toLowerCase()}.${String(r["table_name"]).toLowerCase()}`);
|
|
46
|
+
}
|
|
47
|
+
async describeTable(target) {
|
|
48
|
+
let parts = target.split(".");
|
|
49
|
+
let table = parts.pop() || "";
|
|
50
|
+
let dataset = parts.join(".") || this.defaultNamespace;
|
|
51
|
+
if (!dataset) throw new Error("No dataset specified and no default namespace configured");
|
|
52
|
+
validateBigQueryIdent(dataset);
|
|
53
|
+
let resolvedDataset = await this.resolveDatasetName(dataset);
|
|
54
|
+
let sql = `
|
|
55
|
+
select column_name as column_name, data_type as data_type, ordinal_position as ordinal_position
|
|
56
|
+
from \`${resolvedDataset}.INFORMATION_SCHEMA.COLUMNS\`
|
|
57
|
+
where lower(table_name) = lower(@table)
|
|
58
|
+
order by ordinal_position
|
|
59
|
+
`.trim();
|
|
60
|
+
let res = await this.runQuery(sql, { table });
|
|
61
|
+
return res.rows.map((row) => {
|
|
62
|
+
return { name: String(row["column_name"]).toLowerCase(), dataType: String(row["data_type"]) };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async resolveDatasetName(name) {
|
|
66
|
+
let datasets = await this.listDatasets();
|
|
67
|
+
return datasets.find((ds) => ds.toLowerCase() == name.toLowerCase()) || name;
|
|
68
|
+
}
|
|
69
|
+
async close() {
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export {
|
|
73
|
+
BigQueryConnection
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=bigQuery-OQUNH3VT.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../connections/bigQuery.ts"],
|
|
4
|
+
"sourcesContent": ["import {BigQuery, BigQueryDate, BigQueryTimestamp, type BigQueryOptions} from '@google-cloud/bigquery'\n\nimport {config} from '../../lang/config.ts'\nimport {type QueryConnection, type QueryResult, type SchemaColumn, type QueryParams} from './types.ts'\n\n// BigQuery identifiers can contain letters, numbers, underscores, and hyphens\nfunction validateBigQueryIdent(ident: string) {\n if (!/^[\\w.-]+$/.test(ident)) throw new Error(`Invalid BigQuery identifier: ${ident}`)\n}\n\nexport class BigQueryConnection implements QueryConnection {\n private readonly client: BigQuery\n private readonly projectId: string\n private readonly defaultNamespace?: string\n\n constructor(options: BigQueryOptions = {}) {\n options.projectId ||= config.bigquery?.projectId\n if (!options.projectId) throw new Error('projectId must be set in config or provided in service account credentials')\n this.projectId = options.projectId\n this.client = new BigQuery({...options, userAgent: 'Graphene'})\n this.defaultNamespace = config.defaultNamespace\n }\n\n async runQuery(sql: string, params?: QueryParams): Promise<QueryResult> {\n let [job] = await this.client.createQueryJob({query: sql, useLegacySql: false, params})\n let [rows] = await job.getQueryResults({maxResults: 10000})\n let metadata = job.metadata || (await job.getMetadata())[0]\n let totalRows = Number(metadata?.statistics?.query?.totalRows ?? rows.length)\n\n rows.forEach(r => {\n Object.entries(r).forEach(([k, v]) => {\n if (v instanceof BigQueryTimestamp) r[k] = v.value\n if (v instanceof BigQueryDate) r[k] = v.value\n })\n })\n\n return {rows, totalRows}\n }\n\n async listDatasets(): Promise<string[]> {\n let [datasets] = await this.client.getDatasets()\n return datasets.map(d => String(d.id || d.metadata.datasetReference?.datasetId || '').toLowerCase())\n }\n\n async listTables(dataset?: string): Promise<string[]> {\n if (!dataset) throw new Error('BigQuery requires a dataset')\n validateBigQueryIdent(dataset)\n\n let resolvedDataset = await this.resolveDatasetName(dataset)\n let res = await this.runQuery(`select table_name as table_name\n from \\`${resolvedDataset}.INFORMATION_SCHEMA.TABLES\\`\n where table_type in ('BASE TABLE', 'VIEW') order by table_name`)\n\n return res.rows.map(r => `${resolvedDataset.toLowerCase()}.${String(r['table_name']).toLowerCase()}`)\n }\n\n async describeTable(target: string): Promise<SchemaColumn[]> {\n let parts = target.split('.')\n let table = parts.pop() || ''\n let dataset = parts.join('.') || this.defaultNamespace\n if (!dataset) throw new Error('No dataset specified and no default namespace configured')\n validateBigQueryIdent(dataset)\n let resolvedDataset = await this.resolveDatasetName(dataset)\n let sql = `\n select column_name as column_name, data_type as data_type, ordinal_position as ordinal_position\n from \\`${resolvedDataset}.INFORMATION_SCHEMA.COLUMNS\\`\n where lower(table_name) = lower(@table)\n order by ordinal_position\n `.trim()\n let res = await this.runQuery(sql, {table})\n return res.rows.map(row => {\n return {name: String(row['column_name']).toLowerCase(), dataType: String(row['data_type'])}\n })\n }\n\n async resolveDatasetName(name: string): Promise<string> {\n let datasets = await this.listDatasets()\n return datasets.find(ds => ds.toLowerCase() == name.toLowerCase()) || name\n }\n\n async close(): Promise<void> {}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// ../lang/config.ts
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var config = { dialect: "duckdb", root: "" };
|
|
6
|
+
function setGlobalConfig(cfg) {
|
|
7
|
+
Object.keys(config).forEach((key) => delete config[key]);
|
|
8
|
+
Object.assign(config, normalizeConfig(cfg));
|
|
9
|
+
}
|
|
10
|
+
function normalizeConfig(input, defaultRoot = process.cwd()) {
|
|
11
|
+
let cfg = { ...input };
|
|
12
|
+
if (cfg.namespace && !cfg.defaultNamespace) cfg.defaultNamespace = cfg.namespace;
|
|
13
|
+
let dialect = cfg.dialect || "duckdb";
|
|
14
|
+
if (cfg.bigquery) dialect = "bigquery";
|
|
15
|
+
else if (cfg.snowflake) dialect = "snowflake";
|
|
16
|
+
else if (cfg.clickhouse) dialect = "clickhouse";
|
|
17
|
+
else if (cfg.duckdb) dialect = "duckdb";
|
|
18
|
+
let envFile = [".env"];
|
|
19
|
+
if (Array.isArray(cfg.envFile)) envFile = cfg.envFile;
|
|
20
|
+
else if (cfg.envFile) envFile = [cfg.envFile];
|
|
21
|
+
return {
|
|
22
|
+
...cfg,
|
|
23
|
+
dialect,
|
|
24
|
+
root: path.resolve(cfg.root || defaultRoot),
|
|
25
|
+
port: cfg.port || Number(process.env.GRAPHENE_PORT) || 4e3,
|
|
26
|
+
ignoredFiles: cfg.ignoredFiles || ["**/agents.md", "**/claude.md"],
|
|
27
|
+
envFile
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function loadConfig(dir, envLoader) {
|
|
31
|
+
let configDir = path.resolve(dir);
|
|
32
|
+
while (!existsSync(path.join(configDir, "package.json"))) {
|
|
33
|
+
let parent = path.dirname(configDir);
|
|
34
|
+
if (parent == configDir) throw new Error(`No package.json found in ${path.resolve(dir)} or its parents`);
|
|
35
|
+
configDir = parent;
|
|
36
|
+
}
|
|
37
|
+
let txt = await readFile(path.join(configDir, "package.json"), "utf8");
|
|
38
|
+
let graphene = JSON.parse(txt).graphene;
|
|
39
|
+
if (!graphene || typeof graphene != "object" || Array.isArray(graphene)) {
|
|
40
|
+
throw new Error(`No graphene config found in ${path.join(configDir, "package.json")}`);
|
|
41
|
+
}
|
|
42
|
+
let envFiles = Array.isArray(graphene.envFile) ? graphene.envFile : [graphene.envFile || ".env"];
|
|
43
|
+
envLoader(envFiles.map((file) => path.resolve(configDir, file)));
|
|
44
|
+
let cfg = normalizeConfig({ ...graphene, root: configDir }, configDir);
|
|
45
|
+
return cfg;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
config,
|
|
50
|
+
setGlobalConfig,
|
|
51
|
+
loadConfig
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=chunk-56K2FF57.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../lang/config.ts"],
|
|
4
|
+
"sourcesContent": ["import {existsSync} from 'node:fs'\nimport {readFile} from 'node:fs/promises'\nimport path from 'path'\n\nexport interface Config {\n root: string\n dialect: string\n defaultNamespace?: string\n ignoredFiles: string[]\n telemetry?: boolean\n port?: number\n host?: string\n envFile: string[] // array of paths where we can look for the env file\n\n bigquery?: {\n projectId?: string\n keyPath?: string\n }\n\n snowflake?: {\n account: string\n username: string\n privateKeyPath: string\n schema?: string\n database?: string\n }\n\n clickhouse?: {\n url?: string\n username?: string\n database?: string\n requestTimeout?: number\n }\n\n duckdb?: {\n path?: string\n }\n}\n\nexport type ConfigInput = Omit<Config, 'root' | 'dialect' | 'ignoredFiles' | 'envFile'> & {\n root?: string\n dialect?: Config['dialect']\n ignoredFiles?: Config['ignoredFiles']\n envFile?: string | string[]\n namespace?: string\n}\n\nexport let config: Config = {dialect: 'duckdb', root: ''} as Config\n\nexport function setGlobalConfig(cfg: ConfigInput) {\n Object.keys(config).forEach(key => delete config[key])\n Object.assign(config, normalizeConfig(cfg))\n}\n\nexport function normalizeConfig(input: ConfigInput, defaultRoot = process.cwd()): Config {\n let cfg = {...input}\n if (cfg.namespace && !cfg.defaultNamespace) cfg.defaultNamespace = cfg.namespace\n\n let dialect = cfg.dialect || 'duckdb'\n if (cfg.bigquery) dialect = 'bigquery'\n else if (cfg.snowflake) dialect = 'snowflake'\n else if (cfg.clickhouse) dialect = 'clickhouse'\n else if (cfg.duckdb) dialect = 'duckdb'\n let envFile = ['.env']\n if (Array.isArray(cfg.envFile)) envFile = cfg.envFile\n else if (cfg.envFile) envFile = [cfg.envFile]\n\n return {\n ...cfg,\n dialect,\n root: path.resolve(cfg.root || defaultRoot),\n port: cfg.port || Number(process.env.GRAPHENE_PORT) || 4000,\n ignoredFiles: cfg.ignoredFiles || ['**/agents.md', '**/claude.md'],\n envFile,\n } as Config\n}\n\n// Read graphene config from the nearest parent package.json.\nexport async function loadConfig(dir: string, envLoader: (envFiles: string[]) => void): Promise<Config> {\n // seek upwards from dir looking for package.json\n let configDir = path.resolve(dir)\n while (!existsSync(path.join(configDir, 'package.json'))) {\n let parent = path.dirname(configDir)\n if (parent == configDir) throw new Error(`No package.json found in ${path.resolve(dir)} or its parents`)\n configDir = parent\n }\n\n let txt = await readFile(path.join(configDir, 'package.json'), 'utf8')\n let graphene = JSON.parse(txt).graphene\n if (!graphene || typeof graphene != 'object' || Array.isArray(graphene)) {\n throw new Error(`No graphene config found in ${path.join(configDir, 'package.json')}`)\n }\n\n // config can provide 1 or more env files that Graphene should load. Default to just `.env`\n let envFiles = Array.isArray(graphene.envFile) ? graphene.envFile : [graphene.envFile || '.env']\n envLoader(envFiles.map(file => path.resolve(configDir, file)))\n\n let cfg = normalizeConfig({...graphene, root: configDir}, configDir)\n return cfg\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAQ,kBAAiB;AACzB,SAAQ,gBAAe;AACvB,OAAO,UAAU;AA6CV,IAAI,SAAiB,EAAC,SAAS,UAAU,MAAM,GAAE;AAEjD,SAAS,gBAAgB,KAAkB;AAChD,SAAO,KAAK,MAAM,EAAE,QAAQ,SAAO,OAAO,OAAO,GAAG,CAAC;AACrD,SAAO,OAAO,QAAQ,gBAAgB,GAAG,CAAC;AAC5C;AAEO,SAAS,gBAAgB,OAAoB,cAAc,QAAQ,IAAI,GAAW;AACvF,MAAI,MAAM,EAAC,GAAG,MAAK;AACnB,MAAI,IAAI,aAAa,CAAC,IAAI,iBAAkB,KAAI,mBAAmB,IAAI;AAEvE,MAAI,UAAU,IAAI,WAAW;AAC7B,MAAI,IAAI,SAAU,WAAU;AAAA,WACnB,IAAI,UAAW,WAAU;AAAA,WACzB,IAAI,WAAY,WAAU;AAAA,WAC1B,IAAI,OAAQ,WAAU;AAC/B,MAAI,UAAU,CAAC,MAAM;AACrB,MAAI,MAAM,QAAQ,IAAI,OAAO,EAAG,WAAU,IAAI;AAAA,WACrC,IAAI,QAAS,WAAU,CAAC,IAAI,OAAO;AAE5C,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,MAAM,KAAK,QAAQ,IAAI,QAAQ,WAAW;AAAA,IAC1C,MAAM,IAAI,QAAQ,OAAO,QAAQ,IAAI,aAAa,KAAK;AAAA,IACvD,cAAc,IAAI,gBAAgB,CAAC,gBAAgB,cAAc;AAAA,IACjE;AAAA,EACF;AACF;AAGA,eAAsB,WAAW,KAAa,WAA0D;AAEtG,MAAI,YAAY,KAAK,QAAQ,GAAG;AAChC,SAAO,CAAC,WAAW,KAAK,KAAK,WAAW,cAAc,CAAC,GAAG;AACxD,QAAI,SAAS,KAAK,QAAQ,SAAS;AACnC,QAAI,UAAU,UAAW,OAAM,IAAI,MAAM,4BAA4B,KAAK,QAAQ,GAAG,CAAC,iBAAiB;AACvG,gBAAY;AAAA,EACd;AAEA,MAAI,MAAM,MAAM,SAAS,KAAK,KAAK,WAAW,cAAc,GAAG,MAAM;AACrE,MAAI,WAAW,KAAK,MAAM,GAAG,EAAE;AAC/B,MAAI,CAAC,YAAY,OAAO,YAAY,YAAY,MAAM,QAAQ,QAAQ,GAAG;AACvE,UAAM,IAAI,MAAM,+BAA+B,KAAK,KAAK,WAAW,cAAc,CAAC,EAAE;AAAA,EACvF;AAGA,MAAI,WAAW,MAAM,QAAQ,SAAS,OAAO,IAAI,SAAS,UAAU,CAAC,SAAS,WAAW,MAAM;AAC/F,YAAU,SAAS,IAAI,UAAQ,KAAK,QAAQ,WAAW,IAAI,CAAC,CAAC;AAE7D,MAAI,MAAM,gBAAgB,EAAC,GAAG,UAAU,MAAM,UAAS,GAAG,SAAS;AACnE,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|