@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.
Files changed (26) hide show
  1. package/README.md +65 -29
  2. package/dist/cli/{bigQuery-I3F46SC6.js → bigQuery-OQUNH3VT.js} +2 -2
  3. package/dist/cli/{chunk-QAXEOZ43.js → chunk-56K2FF57.js} +1 -1
  4. package/dist/cli/chunk-56K2FF57.js.map +7 -0
  5. package/dist/cli/{chunk-OVWODUTJ.js → chunk-TZTTALAV.js} +36 -17
  6. package/dist/cli/{chunk-OVWODUTJ.js.map → chunk-TZTTALAV.js.map} +3 -3
  7. package/dist/cli/cli.js +33 -6
  8. package/dist/cli/{clickhouse-ZN5AN2UL.js → clickhouse-S3BJSKND.js} +3 -2
  9. package/dist/cli/clickhouse-S3BJSKND.js.map +7 -0
  10. package/dist/cli/{duckdb-IYBIO5KJ.js → duckdb-TKVMONRK.js} +2 -2
  11. package/dist/cli/{serve2-TNN5EROW.js → serve2-S2LL4D4D.js} +7 -6
  12. package/dist/cli/{serve2-TNN5EROW.js.map → serve2-S2LL4D4D.js.map} +2 -2
  13. package/dist/cli/{snowflake-MOQB5GA4.js → snowflake-3VPDEYYP.js} +2 -2
  14. package/dist/skills/graphene/SKILL.md +7 -0
  15. package/dist/skills/graphene/references/model-gsql.md +19 -21
  16. package/dist/ui/component-utilities/enrich.ts +34 -4
  17. package/dist/ui/internal/LocalApp.svelte +29 -27
  18. package/dist/ui/internal/queryEngine.ts +13 -15
  19. package/dist/ui/internal/runSocket.ts +2 -5
  20. package/dist/ui/web.js +4 -2
  21. package/package.json +5 -1
  22. package/dist/cli/chunk-QAXEOZ43.js.map +0 -7
  23. package/dist/cli/clickhouse-ZN5AN2UL.js.map +0 -7
  24. /package/dist/cli/{bigQuery-I3F46SC6.js.map → bigQuery-OQUNH3VT.js.map} +0 -0
  25. /package/dist/cli/{duckdb-IYBIO5KJ.js.map → duckdb-TKVMONRK.js.map} +0 -0
  26. /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: Map<string, string | string[]>
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: new Map()})
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
- // using Map here because it preserves the order in which we add fields to the select, which we use when we get the result.
48
- let map = new Map(Object.entries(fields))
49
- let exprs: string[] = []
50
- if (map.size > 0) {
51
- map.forEach(value => {
52
- if (Array.isArray(value)) exprs.push(...value)
53
- else exprs.push(value)
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
- } else {
56
- exprs = ['*']
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 = Array.from(node.fields.values()).flatMap(f => f)
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 {type, requestId, action, chart} = JSON.parse(event.data)
46
- if (type !== 'check') return
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.16",
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
- }