@graphenedata/cli 0.0.16 → 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 +65 -29
- package/dist/cli/{bigQuery-I3F46SC6.js → bigQuery-OQUNH3VT.js} +2 -2
- package/dist/cli/{chunk-QAXEOZ43.js → chunk-56K2FF57.js} +1 -1
- package/dist/cli/chunk-56K2FF57.js.map +7 -0
- package/dist/cli/{chunk-OVWODUTJ.js → chunk-TZTTALAV.js} +36 -17
- package/dist/cli/{chunk-OVWODUTJ.js.map → chunk-TZTTALAV.js.map} +3 -3
- package/dist/cli/cli.js +33 -6
- package/dist/cli/{clickhouse-ZN5AN2UL.js → clickhouse-S3BJSKND.js} +3 -2
- package/dist/cli/clickhouse-S3BJSKND.js.map +7 -0
- package/dist/cli/{duckdb-IYBIO5KJ.js → duckdb-TKVMONRK.js} +2 -2
- package/dist/cli/{serve2-TNN5EROW.js → serve2-S2LL4D4D.js} +7 -6
- package/dist/cli/{serve2-TNN5EROW.js.map → serve2-S2LL4D4D.js.map} +2 -2
- package/dist/cli/{snowflake-MOQB5GA4.js → snowflake-3VPDEYYP.js} +2 -2
- package/dist/skills/graphene/SKILL.md +7 -0
- package/dist/skills/graphene/references/model-gsql.md +19 -21
- package/dist/ui/component-utilities/enrich.ts +34 -4
- package/dist/ui/internal/LocalApp.svelte +29 -27
- package/dist/ui/internal/queryEngine.ts +13 -15
- package/dist/ui/internal/runSocket.ts +2 -5
- package/dist/ui/web.js +4 -2
- package/package.json +5 -1
- package/dist/cli/chunk-QAXEOZ43.js.map +0 -7
- package/dist/cli/clickhouse-ZN5AN2UL.js.map +0 -7
- /package/dist/cli/{bigQuery-I3F46SC6.js.map → bigQuery-OQUNH3VT.js.map} +0 -0
- /package/dist/cli/{duckdb-IYBIO5KJ.js.map → duckdb-TKVMONRK.js.map} +0 -0
- /package/dist/cli/{snowflake-MOQB5GA4.js.map → snowflake-3VPDEYYP.js.map} +0 -0
|
@@ -15,7 +15,7 @@ interface QueryNode {
|
|
|
15
15
|
contents: string
|
|
16
16
|
callback?: ResultHandler
|
|
17
17
|
loading: boolean
|
|
18
|
-
fields:
|
|
18
|
+
fields: string[]
|
|
19
19
|
componentId?: string
|
|
20
20
|
error?: GrapheneError
|
|
21
21
|
}
|
|
@@ -39,24 +39,22 @@ export const setQueryFetcher = f => (queryFetcher = f)
|
|
|
39
39
|
// Called by GrapheneQuery tags to register a named query on the page
|
|
40
40
|
function registerQuery(name: string, contents: string) {
|
|
41
41
|
queries = queries.filter(q => q.name !== name)
|
|
42
|
-
queries.push({name, contents, loading: false, fields:
|
|
42
|
+
queries.push({name, contents, loading: false, fields: []})
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Called by viz components to request a particular query of data
|
|
46
46
|
function query(source: string, fields: Record<string, string | string[]>, callback: ResultHandler, componentId?: string) {
|
|
47
|
-
//
|
|
48
|
-
let
|
|
49
|
-
let exprs
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
47
|
+
// Preserve field order because translateData maps result fields back to requested expressions by index.
|
|
48
|
+
let seen = new Set<string>()
|
|
49
|
+
let exprs = Object.values(fields)
|
|
50
|
+
.flatMap(value => (Array.isArray(value) ? value : [value]))
|
|
51
|
+
.filter(field => {
|
|
52
|
+
if (seen.has(field)) return false
|
|
53
|
+
seen.add(field)
|
|
54
|
+
return true
|
|
54
55
|
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
let contents = `from ${source} select ${exprs.join(', ')}`
|
|
59
|
-
queries.push({contents, callback, loading: false, fields: map, source, componentId})
|
|
56
|
+
let contents = `from ${source} select ${(exprs.length ? exprs : ['*']).join(', ')}`
|
|
57
|
+
queries.push({contents, callback, loading: false, fields: exprs, source, componentId})
|
|
60
58
|
runAll()
|
|
61
59
|
return componentId
|
|
62
60
|
}
|
|
@@ -147,7 +145,7 @@ export function translateData(data: any, node: QueryNode): QueryResult {
|
|
|
147
145
|
let rows = data.rows || []
|
|
148
146
|
let fields: Field[] = []
|
|
149
147
|
|
|
150
|
-
let requestFields =
|
|
148
|
+
let requestFields = node.fields
|
|
151
149
|
|
|
152
150
|
data.fields.forEach((field, index) => {
|
|
153
151
|
let requested = requestFields[index]
|
|
@@ -42,17 +42,14 @@ function connect() {
|
|
|
42
42
|
socket.onopen = () => socket!.send(JSON.stringify({type: 'register', url: window.location.href}))
|
|
43
43
|
|
|
44
44
|
socket.onmessage = async event => {
|
|
45
|
-
let {
|
|
46
|
-
|
|
45
|
+
let {requestId, action, chart} = JSON.parse(event.data)
|
|
46
|
+
let finished = await window.$GRAPHENE.waitForLoad(20_000)
|
|
47
47
|
|
|
48
48
|
if (action === 'list') {
|
|
49
|
-
await window.$GRAPHENE.pageReady
|
|
50
|
-
await new Promise(resolve => requestAnimationFrame(resolve))
|
|
51
49
|
socket!.send(JSON.stringify({type: 'checkResponse', requestId, componentIds: listComponentIds()}))
|
|
52
50
|
return
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
let finished = await window.$GRAPHENE.waitForLoad(20_000)
|
|
56
53
|
let screenshot = chart ? await captureChart(chart) : await takeScreenshot()
|
|
57
54
|
socket!.send(JSON.stringify({type: 'checkResponse', requestId, errors: getErrors(), stillLoading: !finished, screenshot}))
|
|
58
55
|
}
|
package/dist/ui/web.js
CHANGED
|
@@ -41,6 +41,7 @@ import LocalApp from './internal/LocalApp.svelte'
|
|
|
41
41
|
// code only has to load once.
|
|
42
42
|
// In theory we could do this with Vite splitting, but then we have a hard dependency on the exact format vite uses. Plus I find the easier to understand.
|
|
43
43
|
window.$GRAPHENE = window.$GRAPHENE || {}
|
|
44
|
+
window.$GRAPHENE.appLoading = false
|
|
44
45
|
|
|
45
46
|
let nextRenderId = 0
|
|
46
47
|
let pendingRenders = new Set()
|
|
@@ -64,11 +65,11 @@ window.$GRAPHENE.waitForLoad = async (timeout = 20_000) => {
|
|
|
64
65
|
let g = window.$GRAPHENE
|
|
65
66
|
let end = Date.now() + timeout
|
|
66
67
|
while (Date.now() < end) {
|
|
67
|
-
if (!g.isQueryLoading() && pendingRenders.size == 0) {
|
|
68
|
+
if (!g.appLoading && !g.isQueryLoading() && pendingRenders.size == 0) {
|
|
68
69
|
if (document.fonts?.ready) await document.fonts.ready
|
|
69
70
|
await new Promise(resolve => setTimeout(resolve, 300))
|
|
70
71
|
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
|
71
|
-
if (!g.isQueryLoading() && pendingRenders.size == 0) return true
|
|
72
|
+
if (!g.appLoading && !g.isQueryLoading() && pendingRenders.size == 0) return true
|
|
72
73
|
}
|
|
73
74
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
74
75
|
}
|
|
@@ -109,5 +110,6 @@ window.$GRAPHENE.components = {
|
|
|
109
110
|
window.$GRAPHENE.svelte = {mount, unmount}
|
|
110
111
|
|
|
111
112
|
if (window.location.pathname.replace(/\/+$/, '') !== '/__ct') {
|
|
113
|
+
window.$GRAPHENE.appLoading = true
|
|
112
114
|
mount(LocalApp, {target: document.body})
|
|
113
115
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphenedata/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"author": "Graphene Systems Inc",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/graphene-data/graphene.git"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"bin": {
|
|
8
12
|
"graphene": "./bin.js"
|
|
@@ -1,7 +0,0 @@
|
|
|
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 }\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;AA4CV,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
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../connections/clickhouse.ts"],
|
|
4
|
-
"sourcesContent": ["import {createClient, type ClickHouseClient} from '@clickhouse/client'\n\nimport {type QueryConnection, type QueryResult, type QueryParams, type SchemaColumn} from './types.ts'\n\nexport interface ClickHouseOptions {\n url: string\n username: string\n password: string\n database?: string\n}\n\nexport class ClickHouseConnection implements QueryConnection {\n private client: ClickHouseClient\n private defaultDatabase: string\n\n constructor(options: ClickHouseOptions) {\n this.defaultDatabase = options.database || 'default'\n this.client = createClient({\n url: options.url,\n username: options.username,\n password: options.password,\n database: this.defaultDatabase,\n application: 'Graphene',\n })\n }\n\n async runQuery(sql: string, _params?: QueryParams): Promise<QueryResult> {\n let result = await this.client.query({query: sql, format: 'JSONEachRow'})\n let rows = (await result.json()) as Array<Record<string, unknown>>\n return {rows, totalRows: rows.length}\n }\n\n async listDatasets(): Promise<string[]> {\n let res = await this.runQuery(`\n select name\n from system.databases\n where lower(name) not in ('system', 'information_schema')\n order by name\n `)\n return res.rows.map(row => String(row['name']).toLowerCase())\n }\n\n async listTables(database = this.defaultDatabase): Promise<string[]> {\n let sql = `\n select database, name\n from system.tables\n where lower(database) = lower('${escapeClickHouseString(database)}')\n order by name\n `.trim()\n let res = await this.runQuery(sql)\n return res.rows.map(row => `${String(row['database']).toLowerCase()}.${String(row['name']).toLowerCase()}`)\n }\n\n async describeTable(target: string): Promise<SchemaColumn[]> {\n let parts = target.split('.').filter(Boolean)\n let table = parts.pop() || ''\n let database = parts.join('.') || this.defaultDatabase\n let sql = `\n select name, type, position\n from system.columns\n where lower(database) = lower('${escapeClickHouseString(database)}')\n and lower(table) = lower('${escapeClickHouseString(table)}')\n order by position\n `.trim()\n let res = await this.runQuery(sql)\n return res.rows.map(row => ({name: String(row['name']).toLowerCase(), dataType: String(row['type'])}))\n }\n\n async close(): Promise<void> {\n await this.client.close()\n }\n}\n\nfunction escapeClickHouseString(value: string) {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAQ,oBAA0C;AAW3C,IAAM,uBAAN,MAAsD;AAAA,EACnD;AAAA,EACA;AAAA,EAER,YAAY,SAA4B;AACtC,SAAK,kBAAkB,QAAQ,YAAY;AAC3C,SAAK,SAAS,aAAa;AAAA,MACzB,KAAK,QAAQ;AAAA,MACb,UAAU,QAAQ;AAAA,MAClB,UAAU,QAAQ;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,KAAa,SAA6C;AACvE,QAAI,SAAS,MAAM,KAAK,OAAO,MAAM,EAAC,OAAO,KAAK,QAAQ,cAAa,CAAC;AACxE,QAAI,OAAQ,MAAM,OAAO,KAAK;AAC9B,WAAO,EAAC,MAAM,WAAW,KAAK,OAAM;AAAA,EACtC;AAAA,EAEA,MAAM,eAAkC;AACtC,QAAI,MAAM,MAAM,KAAK,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,KAK7B;AACD,WAAO,IAAI,KAAK,IAAI,SAAO,OAAO,IAAI,MAAM,CAAC,EAAE,YAAY,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,WAAW,WAAW,KAAK,iBAAoC;AACnE,QAAI,MAAM;AAAA;AAAA;AAAA,uCAGyB,uBAAuB,QAAQ,CAAC;AAAA;AAAA,MAEjE,KAAK;AACP,QAAI,MAAM,MAAM,KAAK,SAAS,GAAG;AACjC,WAAO,IAAI,KAAK,IAAI,SAAO,GAAG,OAAO,IAAI,UAAU,CAAC,EAAE,YAAY,CAAC,IAAI,OAAO,IAAI,MAAM,CAAC,EAAE,YAAY,CAAC,EAAE;AAAA,EAC5G;AAAA,EAEA,MAAM,cAAc,QAAyC;AAC3D,QAAI,QAAQ,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO;AAC5C,QAAI,QAAQ,MAAM,IAAI,KAAK;AAC3B,QAAI,WAAW,MAAM,KAAK,GAAG,KAAK,KAAK;AACvC,QAAI,MAAM;AAAA;AAAA;AAAA,uCAGyB,uBAAuB,QAAQ,CAAC;AAAA,oCACnC,uBAAuB,KAAK,CAAC;AAAA;AAAA,MAE3D,KAAK;AACP,QAAI,MAAM,MAAM,KAAK,SAAS,GAAG;AACjC,WAAO,IAAI,KAAK,IAAI,UAAQ,EAAC,MAAM,OAAO,IAAI,MAAM,CAAC,EAAE,YAAY,GAAG,UAAU,OAAO,IAAI,MAAM,CAAC,EAAC,EAAE;AAAA,EACvG;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B;AACF;AAEA,SAAS,uBAAuB,OAAe;AAC7C,SAAO,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACzD;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|