@graphenedata/cli 0.0.16 → 0.0.18

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 (38) hide show
  1. package/README.md +65 -29
  2. package/dist/cli/{bigQuery-I3F46SC6.js → bigQuery-YIWXZPY6.js} +2 -2
  3. package/dist/cli/{chunk-QAXEOZ43.js → chunk-SQVXTHE5.js} +2 -2
  4. package/dist/cli/chunk-SQVXTHE5.js.map +7 -0
  5. package/dist/cli/{chunk-OVWODUTJ.js → chunk-UTV3ERGI.js} +279 -150
  6. package/dist/cli/chunk-UTV3ERGI.js.map +7 -0
  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-V6PJEA7H.js} +2 -2
  11. package/dist/cli/{serve2-TNN5EROW.js → serve2-CGQSM7TD.js} +7 -6
  12. package/dist/cli/{serve2-TNN5EROW.js.map → serve2-CGQSM7TD.js.map} +2 -2
  13. package/dist/cli/{snowflake-MOQB5GA4.js → snowflake-HVSTYBLB.js} +2 -2
  14. package/dist/index.d.ts +4 -4
  15. package/dist/lang/index.d.ts +4 -4
  16. package/dist/skills/graphene/SKILL.md +10 -3
  17. package/dist/skills/graphene/references/gsql.md +26 -23
  18. package/dist/skills/graphene/references/model-gsql.md +19 -21
  19. package/dist/ui/component-utilities/enrich.ts +88 -23
  20. package/dist/ui/component-utilities/format.ts +36 -21
  21. package/dist/ui/component-utilities/theme.ts +0 -1
  22. package/dist/ui/components/AreaChart.svelte +1 -1
  23. package/dist/ui/components/BarChart.svelte +1 -1
  24. package/dist/ui/components/LineChart.svelte +1 -1
  25. package/dist/ui/internal/LocalApp.svelte +29 -27
  26. package/dist/ui/internal/PageNavGroup.svelte +2 -2
  27. package/dist/ui/internal/Sidebar.svelte +7 -7
  28. package/dist/ui/internal/queryEngine.ts +13 -15
  29. package/dist/ui/internal/runSocket.ts +2 -5
  30. package/dist/ui/internal/sidebar.svelte.js +11 -1
  31. package/dist/ui/web.js +4 -2
  32. package/package.json +5 -1
  33. package/dist/cli/chunk-OVWODUTJ.js.map +0 -7
  34. package/dist/cli/chunk-QAXEOZ43.js.map +0 -7
  35. package/dist/cli/clickhouse-ZN5AN2UL.js.map +0 -7
  36. /package/dist/cli/{bigQuery-I3F46SC6.js.map → bigQuery-YIWXZPY6.js.map} +0 -0
  37. /package/dist/cli/{duckdb-IYBIO5KJ.js.map → duckdb-V6PJEA7H.js.map} +0 -0
  38. /package/dist/cli/{snowflake-MOQB5GA4.js.map → snowflake-HVSTYBLB.js.map} +0 -0
package/dist/cli/cli.js CHANGED
@@ -20,12 +20,12 @@ import {
20
20
  runServeInBackground,
21
21
  stopGrapheneIfRunning,
22
22
  toSql
23
- } from "./chunk-OVWODUTJ.js";
23
+ } from "./chunk-UTV3ERGI.js";
24
24
  import {
25
25
  config,
26
26
  loadConfig,
27
27
  setGlobalConfig
28
- } from "./chunk-QAXEOZ43.js";
28
+ } from "./chunk-SQVXTHE5.js";
29
29
 
30
30
  // cli.ts
31
31
  import { Command } from "commander";
@@ -97,15 +97,16 @@ program.command("compile").description("Translate a query to SQL and print it").
97
97
  console.log(toSql(query));
98
98
  })
99
99
  );
100
- program.command("run").description("Run a query or screenshot a Graphene page").argument("[input]", 'Path to file, a raw string, or "-" for stdin').option("-c, --chart <chartTitleOrComponentId>", "Title or component ID of a specific chart to capture").option("-q, --query <queryName>", "Query or table name to run from a markdown page").action(
100
+ program.command("run").description("Run a query or screenshot a Graphene page").argument("[input]", 'Path to file, a raw string, or "-" for stdin').option("-c, --chart <chartTitleOrComponentId>", "Title or component ID of a specific chart to capture").option("-q, --query <queryName>", "Query or table name to run from a markdown page").option("--input <key=value>", "Input value to use for parameters; repeat for multiple values", (value, previous) => previous.concat(value), []).action(
101
101
  withTelemetry("run", async (exit, input, options) => {
102
102
  if (options.chart && options.query) {
103
103
  console.error("Cannot use --chart and --query together");
104
104
  return exit(1);
105
105
  }
106
+ let inputs = parseRunInputs(options.input || [], exit);
106
107
  let inputPath = getExistingPath(input);
107
108
  if (inputPath && inputPath.endsWith(".md")) {
108
- let res2 = options.query ? await runNamedQueryFromMd(inputPath, options.query, telemetry) : await runMdFile({ mdArg: inputPath, chart: options.chart, telemetry });
109
+ let res2 = options.query ? await runNamedQueryFromMd(inputPath, options.query, { inputs, telemetry }) : await runMdFile({ mdArg: inputPath, chart: options.chart, inputs, telemetry });
109
110
  return exit(res2 ? 0 : 1);
110
111
  }
111
112
  if (options.chart || options.query) {
@@ -121,7 +122,7 @@ program.command("run").description("Run a query or screenshot a Graphene page").
121
122
  let gsql = await readInput(input);
122
123
  let analysis = analyzeWorkspace({ config, files: files.filter((file) => file.path != "input").concat({ path: "input", contents: gsql }) }, "input");
123
124
  let [query] = validateInputQuery(analysis, exit);
124
- let sql = toSql(query);
125
+ let sql = renderSql(query, inputs, exit);
125
126
  let res = await runQuery(sql);
126
127
  printTable(res.rows);
127
128
  })
@@ -192,7 +193,7 @@ program.command("serve").description("Run the local server").option("--bg", "Run
192
193
  await runServeInBackground();
193
194
  return exit(0);
194
195
  } else {
195
- let mod = await import("./serve2-TNN5EROW.js");
196
+ let mod = await import("./serve2-CGQSM7TD.js");
196
197
  await mod.serve2(telemetry);
197
198
  }
198
199
  })
@@ -249,6 +250,32 @@ function validateInputQuery(analysis, exit) {
249
250
  }
250
251
  return queries;
251
252
  }
253
+ function parseRunInputs(values, exit) {
254
+ let inputs = {};
255
+ for (let value of values) {
256
+ let index = value.indexOf("=");
257
+ let key = index >= 0 ? value.slice(0, index) : "";
258
+ if (index < 0 || !key) {
259
+ console.error(`Invalid --input "${value}". Expected key=value.`);
260
+ return exit(1);
261
+ }
262
+ let next = value.slice(index + 1);
263
+ let existing = inputs[key];
264
+ if (existing === void 0) inputs[key] = next;
265
+ else if (Array.isArray(existing)) existing.push(next);
266
+ else inputs[key] = [existing, next];
267
+ }
268
+ return inputs;
269
+ }
270
+ function renderSql(query, inputs, exit) {
271
+ try {
272
+ return toSql(query, inputs);
273
+ } catch (err) {
274
+ if (err instanceof Error) console.error(err.message);
275
+ else console.error(String(err));
276
+ return exit(1);
277
+ }
278
+ }
252
279
  function findCaseInsensitive(values, needle) {
253
280
  return values.find((value) => value.toLowerCase() == needle.toLowerCase()) || null;
254
281
  }
@@ -10,7 +10,8 @@ var ClickHouseConnection = class {
10
10
  username: options.username,
11
11
  password: options.password,
12
12
  database: this.defaultDatabase,
13
- application: "Graphene"
13
+ application: "Graphene",
14
+ request_timeout: options.requestTimeout
14
15
  });
15
16
  }
16
17
  async runQuery(sql, _params) {
@@ -61,4 +62,4 @@ function escapeClickHouseString(value) {
61
62
  export {
62
63
  ClickHouseConnection
63
64
  };
64
- //# sourceMappingURL=clickhouse-ZN5AN2UL.js.map
65
+ //# sourceMappingURL=clickhouse-S3BJSKND.js.map
@@ -0,0 +1,7 @@
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 requestTimeout?: number\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 request_timeout: options.requestTimeout,\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;AAY3C,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,MACb,iBAAiB,QAAQ;AAAA,IAC3B,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
+ }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  config
3
- } from "./chunk-QAXEOZ43.js";
3
+ } from "./chunk-SQVXTHE5.js";
4
4
 
5
5
  // connections/duckdb.ts
6
6
  import { DuckDBTimestampValue, DuckDBInstance, DuckDBDateValue, DuckDBDecimalValue } from "@duckdb/node-api";
@@ -84,4 +84,4 @@ var DuckDBConnection = class {
84
84
  export {
85
85
  DuckDBConnection
86
86
  };
87
- //# sourceMappingURL=duckdb-IYBIO5KJ.js.map
87
+ //# sourceMappingURL=duckdb-V6PJEA7H.js.map
@@ -6,10 +6,10 @@ import {
6
6
  runQuery,
7
7
  runVitePlugin,
8
8
  toSql
9
- } from "./chunk-OVWODUTJ.js";
9
+ } from "./chunk-UTV3ERGI.js";
10
10
  import {
11
11
  config
12
- } from "./chunk-QAXEOZ43.js";
12
+ } from "./chunk-SQVXTHE5.js";
13
13
 
14
14
  // serve2.ts
15
15
  import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
@@ -29,8 +29,9 @@ import JSON5 from "json5";
29
29
  import path from "path";
30
30
  import sanitizeHtml from "sanitize-html";
31
31
  import { visit } from "unist-util-visit";
32
- function escapeHtml(str) {
33
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
32
+ function svelteStringAttr(str) {
33
+ let literal = str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/&/g, "\\u0026").replace(/"/g, "\\u0022").replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
34
+ return `{\`${literal}\`}`;
34
35
  }
35
36
  function liftInlineEChartsConfig(content) {
36
37
  return content.replace(/<ECharts\b([^>]*)>([\s\S]*?)<\/ECharts>/g, (match, attrs = "", body = "") => {
@@ -48,7 +49,7 @@ function extractQueries() {
48
49
  if (index === null) return;
49
50
  let name = typeof node.meta === "string" ? node.meta : "";
50
51
  let code = typeof node.value === "string" ? node.value.trim() : "";
51
- parent.children[index] = { type: "html", value: `<GrapheneQuery name="${escapeHtml(name)}" code="${escapeHtml(code)}" />` };
52
+ parent.children[index] = { type: "html", value: `<GrapheneQuery name="${svelteStringAttr(name)}" code="${svelteStringAttr(code)}" />` };
52
53
  });
53
54
  };
54
55
  }
@@ -444,4 +445,4 @@ export {
444
445
  serve2,
445
446
  svelteWarnings
446
447
  };
447
- //# sourceMappingURL=serve2-TNN5EROW.js.map
448
+ //# sourceMappingURL=serve2-CGQSM7TD.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../serve2.ts", "../../mdCompile.ts"],
4
- "sourcesContent": ["import {svelte, vitePreprocess} from '@sveltejs/vite-plugin-svelte'\nimport crypto from 'crypto'\nimport fs from 'fs-extra'\n// import sveltePreprocess from 'svelte-preprocess' // this would be nice, but it breaks sourcemaps by default\nimport {type IncomingMessage, type ServerResponse} from 'http'\nimport {mdsvex} from 'mdsvex'\nimport {createRequire} from 'module'\nimport path from 'path'\nimport {fileURLToPath} from 'url'\nimport {createServer, type InlineConfig, optimizeDeps, resolveConfig, type ViteDevServer} from 'vite'\n\nimport type {AnalysisResult, WorkspaceFileInput} from '../lang/types.ts'\n\nimport {config} from '../lang/config.ts'\nimport {analyzeWorkspace, loadWorkspace, toSql} from '../lang/core.ts'\nimport {runQuery} from './connections/index.ts'\nimport {extractFrontmatter, injectComponentImports, remarkPlugins, rehypePlugins} from './mdCompile.ts'\nimport {mockFileMap} from './mockFiles.ts'\nimport {runVitePlugin} from './run.ts'\nimport {getWorkspaceScanCounts, type CliTelemetry} from './telemetry/index.ts'\n\n// Collect Svelte compiler warnings for test assertions\nexport type SvelteWarning = {code: string; message: string; filename?: string}\nexport const svelteWarnings: SvelteWarning[] = []\nexport function clearSvelteWarnings() {\n svelteWarnings.length = 0\n}\n\n// Bump this whenever the query response shape changes so client caches invalidate.\nconst QUERY_VERSION = 1\n\nlet uiRoot: string\nlet nodeRequire = createRequire(import.meta.url)\n\nexport async function serve2(telemetry?: CliTelemetry): Promise<ViteDevServer> {\n let server = await createServer(await createConfig(telemetry))\n // I originally added this to avoid the page refreshing immediately on load.\n // We def don't want to run it in tests, because its not safe to do in parallel.\n // I'm not sure it's still needed, now that we explicitly list out `optimizeDeps.includes`, refreshes should be rare\n // await optimizeDeps(server.config, true)\n await server.listen()\n console.log(`Server running at http://localhost:${server.config.server.port}`)\n\n return server\n}\n\nasync function createConfig(telemetry?: CliTelemetry): Promise<InlineConfig> {\n uiRoot = path.join(fileURLToPath(import.meta.url), '../../ui')\n let port = Number(process.env.GRAPHENE_PORT) || 4000\n let svelteRoot = path.dirname(nodeRequire.resolve('svelte/package.json'))\n let sveltePackage = nodeRequire('svelte/package.json')\n let svelteDependencyRoot = path.dirname(svelteRoot)\n let svelteExport = (name: string) => path.join(svelteRoot, sveltePackage.exports[name].browser || sveltePackage.exports[name].default)\n let packaged = path.basename(path.dirname(uiRoot)) == 'dist'\n await fs.ensureDir(path.resolve(config.root, 'node_modules/.graphene'))\n\n // Bind to 0.0.0.0 when running in a container so port forwarding works from the host\n let inContainer = fs.existsSync('/.dockerenv')\n let host = inContainer ? '0.0.0.0' : '127.0.0.1'\n\n return {\n root: config.root,\n logLevel: process.env.NODE_ENV == 'test' ? 'silent' : 'info',\n plugins: [\n svelte({\n configFile: false,\n extensions: ['.svelte', '.md'],\n preprocess: [\n vitePreprocess(),\n mdsvex({\n extensions: ['.md'],\n remarkPlugins,\n rehypePlugins,\n }) as any,\n injectComponentImports(),\n ],\n onwarn(warning, defaultHandler) {\n if (process.env.NODE_ENV === 'test') {\n svelteWarnings.push({code: warning.code, message: warning.message, filename: warning.filename})\n }\n defaultHandler?.(warning) // Still call the default handler to print warnings\n },\n }),\n fixSvelteDepsInTests(),\n fixHmrForFailedModules(),\n runVitePlugin(),\n handleRequestPlugin,\n updateWorkspacePlugin(telemetry),\n mockFilesForTests(),\n ],\n publicDir: path.resolve(uiRoot, 'public'),\n // on the fence about this one. This would make it less likely we need to optimize when alternating between dev and tests.\n // cacheDir: process.env.NODE_ENV == 'test' ? 'node_modules/.vite-tests' : 'node_modules/.vite',\n server: {\n port,\n host,\n fs: {strict: false},\n strictPort: true,\n hmr: {overlay: false}, // we handle compilation errors ourselves (see LocalApp.svelte)\n },\n resolve: {\n alias: [\n {find: /^graphene$/, replacement: path.resolve(uiRoot, 'web.js')},\n // Vite runs in a user project, but svelte is a direct dependency of the cli, and thus transitive to the user project.\n // So when Vite tries to resolve `svelte` from a compiled md page, it can't find it without these aliases.\n {find: /^svelte$/, replacement: svelteExport('.')},\n {find: /^svelte\\/animate$/, replacement: svelteExport('./animate')},\n {find: /^svelte\\/attachments$/, replacement: svelteExport('./attachments')},\n {find: /^svelte\\/easing$/, replacement: svelteExport('./easing')},\n {find: /^svelte\\/events$/, replacement: svelteExport('./events')},\n {find: /^svelte\\/internal$/, replacement: svelteExport('./internal')},\n {find: /^svelte\\/internal\\/client$/, replacement: svelteExport('./internal/client')},\n {find: /^svelte\\/internal\\/disclose-version$/, replacement: svelteExport('./internal/disclose-version')},\n {find: /^svelte\\/internal\\/flags\\/async$/, replacement: svelteExport('./internal/flags/async')},\n {find: /^svelte\\/internal\\/flags\\/legacy$/, replacement: svelteExport('./internal/flags/legacy')},\n {find: /^svelte\\/internal\\/flags\\/tracing$/, replacement: svelteExport('./internal/flags/tracing')},\n {find: /^svelte\\/legacy$/, replacement: svelteExport('./legacy')},\n {find: /^svelte\\/motion$/, replacement: svelteExport('./motion')},\n {find: /^svelte\\/reactivity$/, replacement: svelteExport('./reactivity')},\n {find: /^svelte\\/reactivity\\/window$/, replacement: svelteExport('./reactivity/window')},\n {find: /^svelte\\/store$/, replacement: svelteExport('./store')},\n {find: /^svelte\\/transition$/, replacement: svelteExport('./transition')},\n {find: /^clsx$/, replacement: path.join(svelteDependencyRoot, 'clsx/dist/clsx.mjs')},\n ],\n },\n\n optimizeDeps: {\n noDiscovery: process.env.NODE_ENV == 'test', // tests manually optimize before starting test workers\n exclude: ['virtual:nav'], // provided by a plugin, so don't try and optimize it\n // Vite running in a user project will not naturally discover and optimize these transitive deps.\n // When you launch the server, your first page load will automatically refresh after a second or two as Vite now sees and optimizes these.\n // This line makes it do that up-front, avoiding that reload jank. The packaged CLI also pre-bundles the `graphene` alias itself;\n // doing that from source causes trouble in examples/tests because the alias points outside node_modules.\n // `graphene` here is a special case: when packaged up it is considered a dependency, but in examples/tests, including it would cause errors.\n // oxfmt-ignore\n include: [\n ...(packaged ? ['graphene'] : []),\n '@graphenedata/cli > svelte',\n '@graphenedata/cli > chroma-js',\n '@graphenedata/cli > echarts',\n '@graphenedata/cli > @graphenedata/html2canvas',\n '@graphenedata/cli > @graphenedata/ui > svelte',\n '@graphenedata/cli > @graphenedata/ui > chroma-js',\n '@graphenedata/cli > @graphenedata/ui > echarts/dist/echarts.esm.js',\n '@graphenedata/cli > @graphenedata/ui > @graphenedata/html2canvas',\n ],\n },\n }\n}\n\nasync function handleQuery(req: IncomingMessage, res: ServerResponse<IncomingMessage>) {\n let chunks = [] as any[]\n for await (let chunk of req) chunks.push(chunk)\n let {gsql, params, hashes} = JSON.parse(Buffer.concat(chunks).toString())\n res.setHeader('Content-Type', 'application/json')\n\n await workspaceLoadPromise\n\n // queries should not analyze md files\n let gsqlFiles = workspaceFiles.filter(file => !file.path.endsWith('.md'))\n let result = analyzeWorkspace({config, files: [...gsqlFiles, {path: 'input', contents: gsql}]})\n updateParsedFiles(result)\n\n let diagnostics = result.diagnostics\n if (diagnostics.length) {\n res.statusCode = 400\n res.end(JSON.stringify(diagnostics[0]))\n return\n }\n\n let queries = result.files.find(file => file.path == 'input')?.queries || []\n if (queries.length > 1) throw new Error('Found multiple queries, which could be a parsing error')\n let sql = toSql(queries[0], params)\n\n // If the client already has this data, dont run the query\n let hash = crypto.createHash('SHA1').update(`query-v${QUERY_VERSION}|${sql}`).digest('hex')\n res.setHeader('ETag', hash)\n if (hashes.includes(hash) && req.headers['cache-control'] != 'no-cache') {\n res.statusCode = 304\n return res.end()\n }\n\n let queryResults = await runQuery(sql)\n let totalRows = queryResults.totalRows ?? queryResults.rows.length\n if (totalRows > queryResults.rows.length) throw new Error('Query returns too many rows')\n let fields = queries[0].fields.map(field => ({name: field.name, type: field.type, metadata: field.metadata || {}}))\n res.end(JSON.stringify({rows: queryResults.rows, hash, fields, sql}))\n}\n\nasync function handlePage(server: ViteDevServer, res: ServerResponse<IncomingMessage>) {\n res.setHeader('Content-Type', 'text/html')\n\n // Use a .html URL for transformIndexHtml so Vite doesn't run the svelte plugin on our HTML template.\n let html = await server.transformIndexHtml(\n '/index.html',\n `<!doctype html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Graphene</title>\n <link rel=\"icon\" href=\"/favicon.ico\" />\n </head>\n <body>\n <script type=\"module\">\n import 'graphene'\n </script>\n </body>\n </html>`,\n )\n return res.end(html)\n}\n\n// Runs vite's pre-bundling of dependencies. Used by tests to do this once, instead of for each worker.\nexport async function prepareDeps() {\n let cfg = await resolveConfig(await createConfig(), 'serve')\n await optimizeDeps(cfg, true)\n}\n\n// Svelte forces optimizeDeps whenever its own metadata has changed.\n// For tests, we already optimizeDeps before any tests start up, so we don't need this, and it causes problems\n// if multiple workers are all trying to optimizeDeps at the same time (vite isn't exactly concurrency-safe).\nfunction fixSvelteDepsInTests() {\n let viteConfig: any\n\n function configResolved(cfg: any) {\n viteConfig = cfg\n }\n\n // This must run AFTER Svelte's buildStart which sets force=true based on metadata changes.\n // Using enforce:'post' and sequential:true ensures we run last and can override.\n function buildStart() {\n if (process.env.NODE_ENV != 'test') return\n viteConfig.optimizeDeps.force = false\n }\n buildStart.sequential = true // force running after other sequential hooks (like svelte's)\n return {name: 'fix-svelte-deps', enforce: 'post' as const, configResolved, buildStart}\n}\n\n// When a module's transform fails (e.g. Svelte compilation error in an md file), Vite's import analysis\n// never runs on it, leaving `isSelfAccepting` as undefined. Vite's `propagateUpdate` silently skips\n// unanalyzed modules, so fixing the file produces no HMR update and the page stays broken.\n// We detect this and send a full-reload instead, since the module was never successfully loaded\n// and can't be hot-swapped.\nfunction fixHmrForFailedModules() {\n return {\n name: 'fix-hmr-for-failed-modules',\n hotUpdate(this: any, {modules}: {modules: any[]}) {\n // When a module's last transform failed, its transformResult is null. Vite's normal HMR can't\n // hot-swap a module that has no valid transform \u2014 either because it was never analyzed\n // (isSelfAccepting === undefined) or because it was previously working but is now broken.\n // In both cases, force a full page reload so the browser re-requests everything fresh.\n let hasFailed = modules.some(m => !m.transformResult)\n if (hasFailed) {\n this.environment.hot.send({type: 'full-reload', path: '*'})\n return []\n }\n },\n }\n}\n\n// Watch for changes to gsql files and reload the workspace.\n// This reload blocks all requests, so we shouldn't ever analyze without a workspace.\n// Also tracks all the md files in the workspace to populate the nav sidebar\nlet workspaceLoadPromise: Promise<void> | undefined\nlet workspaceFiles: WorkspaceFileInput[] = []\nlet mdFiles: {path: string; title?: string}[] = []\nfunction updateWorkspacePlugin(telemetry?: CliTelemetry) {\n return {\n name: 'updateWorkspace',\n resolveId(id: string) {\n if (id == 'virtual:nav') return '\\0virtual:nav'\n },\n load(id: string) {\n if (id != '\\0virtual:nav') return\n\n // in tests, inject mock files into the nav.\n // we do this on `load` as each test doesn't always refresh the workspace\n // TODO, we should prob inject these into `loadWorkspace`, then we wouldn't need this block at all\n let res = [...mdFiles]\n if (process.env.NODE_ENV == 'test') {\n for (let [path, contents] of Object.entries(mockFileMap)) {\n let mockFile = {path, title: extractFrontmatter(contents).title}\n let idx = res.findIndex(file => file.path == path)\n if (idx >= 0) res.splice(idx, 1, mockFile)\n else res.push(mockFile)\n }\n }\n\n return `export default ${JSON.stringify(res)}`\n },\n configureServer: (s: ViteDevServer) => {\n let refresh = async () => {\n workspaceLoadPromise = (async () => {\n let loaded = await loadWorkspace(config.root, true, config.ignoredFiles)\n telemetry?.event('workspace_scanned', {command: 'serve', ...getWorkspaceScanCounts(loaded)})\n workspaceFiles = loaded.map(file => {\n let existing = workspaceFiles.find(existing => existing.path == file.path && existing.contents == file.contents)\n return existing?.parsed ? {...file, parsed: existing.parsed} : file\n })\n })()\n await workspaceLoadPromise\n\n // store md file path/title so we can serve it as virtual:nav for the sidebar\n mdFiles = workspaceFiles.filter(file => file.path.endsWith('.md')).map(f => ({path: f.path, title: extractFrontmatter(f.contents).title}))\n\n let mod = s.moduleGraph.getModuleById('\\0virtual:nav')\n if (!mod) return\n s.reloadModule(mod) // triggers HMR of any `virtual:nav` imports\n }\n\n s.watcher.add(['**/*.gsql', '**/*.md'])\n s.watcher.on('all', refresh)\n refresh()\n },\n }\n}\n\nfunction updateParsedFiles(analysis: AnalysisResult) {\n workspaceFiles = workspaceFiles.map(file => {\n let analyzed = analysis.files.find(next => next.path == file.path)\n if (!analyzed) return file\n return {\n ...file,\n parsed: {\n tree: analyzed.tree!,\n virtualContents: analyzed.virtualContents,\n virtualToMarkdownOffset: analyzed.virtualToMarkdownOffset,\n },\n }\n })\n}\n\nconst handleRequestPlugin = {\n name: 'handleRequest',\n configureServer: (s: ViteDevServer) => {\n s.middlewares.use(async function handleRequest(req, res, next) {\n try {\n let [pathName] = (req.url || '').split('?')\n if (pathName == '/_api/query') return await handleQuery(req, res)\n if (pathName) if (pathName == '/__ct' || pathName == '/_charts' || pathName == '/_styles') return await handlePage(s, res)\n\n if (!pathName || pathName == '/') pathName = 'index'\n let relativeMdPath = pathName.replace(/^\\//, '') + '.md'\n let mdPath = path.join(config.root, relativeMdPath)\n\n if (mockFileMap[relativeMdPath] || (await fs.exists(mdPath))) {\n await handlePage(s, res)\n } else {\n next()\n }\n } catch (err: any) {\n if (process.env.NODE_ENV != 'test') console.error(err) // ignore in tests because they're noisy, and any unexpected errors should be captured by browserConsole.\n res.statusCode = 500\n res.end(JSON.stringify({message: err.message, stack: err.stack}))\n }\n })\n },\n}\n\nfunction mockFilesForTests() {\n if (process.env.NODE_ENV !== 'test') return null\n\n function toMockKey(id: string) {\n // Handle both absolute paths (/wt/.../index.md) and root-relative paths (/index.md)\n return id.replace(config.root + '/', '').replace(/^\\//, '')\n }\n\n return {\n name: 'mock-files-for-tests',\n enforce: 'pre' as const,\n resolveId(id: any) {\n if (!mockFileMap[toMockKey(id)]) return\n // Always resolve to the absolute path so the module graph key matches\n // what updateMockFile emits via server.watcher (needed for HMR to work).\n return path.join(config.root, toMockKey(id)) + '?mock'\n },\n load(id: any) {\n if (!id.endsWith('?mock')) return null\n return mockFileMap[toMockKey(id.replace(/\\?mock$/, ''))]\n },\n }\n}\n", "import type {Plugin} from 'unified'\n\nimport {decodeHTML} from 'entities'\nimport fs from 'fs'\nimport yaml from 'js-yaml'\nimport JSON5 from 'json5'\nimport path from 'path'\nimport sanitizeHtml from 'sanitize-html'\nimport {visit} from 'unist-util-visit'\n\nfunction escapeHtml(str: string) {\n return str.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\n// Takes the contents of a <ECharts> tag, and json5 parses it\nexport function liftInlineEChartsConfig(content: string) {\n return content.replace(/<ECharts\\b([^>]*)>([\\s\\S]*?)<\\/ECharts>/g, (match: string, attrs = '', body = '') => {\n let inline = body.trim()\n if (!inline) return match\n if (/\\sconfig\\s*=/.test(attrs)) return match\n let source = inline.startsWith('{') ? inline : `{${inline}}`\n let config = JSON.stringify(JSON5.parse(source), (_key, value) => (typeof value == 'string' ? decodeHTML(value) : value))\n return `<ECharts${attrs} config={${config}}></ECharts>`\n })\n}\n\n// Turn code fences into <GrapheneQuery> tags, which register those queries\nexport function extractQueries() {\n return function transformer(tree: any) {\n visit(tree, 'code', (node, index, parent) => {\n if (index === null) return\n let name = typeof node.meta === 'string' ? node.meta : ''\n let code = typeof node.value === 'string' ? node.value.trim() : ''\n parent.children[index] = {type: 'html', value: `<GrapheneQuery name=\"${escapeHtml(name)}\" code=\"${escapeHtml(code)}\" />`}\n })\n }\n}\n\n// remark will leave less-than and greater-than unescaped, which breaks svelte and prevents the page from loading.\nexport function escapeAngles() {\n return function transformer(tree: any) {\n visit(tree, 'text', (node: any) => {\n if (!node.value || typeof node.value !== 'string') return\n if (!node.value.includes('<')) return\n node.value = node.value.replace(/</g, '&lt;')\n })\n }\n}\n\n// remark can split one html block into adjacent html nodes when self-closing tags are involved.\n// Merge those sibling html nodes so downstream rehype/sanitize work on the full block.\nexport function mergeAdjacentHtml() {\n return function transformer(tree: any) {\n visit(tree, (parent: any) => {\n if (!Array.isArray(parent?.children)) return\n\n for (let i = 0; i < parent.children.length; i++) {\n if (parent.children[i]?.type !== 'html') continue\n\n let j = i\n while (j + 1 < parent.children.length && parent.children[j + 1]?.type === 'html') j++\n if (j == i) continue\n\n let value = parent.children\n .slice(i, j + 1)\n .map((node: any) => node.value || '')\n .join('\\n')\n parent.children.splice(i, j - i + 1, {type: 'html', value})\n }\n })\n }\n}\n\n// Restrict allowed components in markdown files to avoid xss issues.\n// This uses sanitize-html rather than rehype-sanitize because the latter had lots of issues with preserving tag casing,\n// as well as allowing all attributes on our allowlisted components.\nexport function sanitizeMarkdown() {\n return function transformer(tree: any) {\n visit(tree, 'raw', (node: any) => {\n if (typeof node.value !== 'string') return\n\n // sanitize-html doesn't like non-standard self-closing tags, so we need to rewrite them into open+close tags\n let expanded = node.value.replace(/<(\\w+)((?:\\s[^<>]*?)?)\\s*\\/>/gi, (_: string, name: string, attrs = '') => {\n let spacing = attrs\n return `<${name}${spacing}></${name}>`\n })\n\n let sanitized = sanitizeHtml(expanded, {\n ...sanitizeHtml.defaults,\n allowedTags: [...sanitizeHtml.defaults.allowedTags, ...componentNames()],\n allowedAttributes: {\n ...sanitizeHtml.defaults.allowedAttributes,\n ...Object.fromEntries(componentNames().map(n => [n, ['*']])),\n },\n parser: {\n ...((sanitizeHtml.defaults as any).parser || {}),\n lowerCaseAttributeNames: false,\n lowerCaseTags: false,\n },\n })\n node.value = sanitized\n })\n }\n}\n\n// We don't want users to have to manually import components in their md files, so we auto-import them.\nexport function injectComponentImports() {\n let imp = `const {${componentNames().join(', ')}} = window.$GRAPHENE.components`\n\n return {\n markup: ({content, filename}: {content: string; filename: string}) => {\n if (!filename.endsWith('.md')) return // only auto-import components for md files\n content = liftInlineEChartsConfig(content)\n if (content.includes('<script>')) {\n content = content.replace('<script>', `<script>\\n${imp}`)\n } else {\n content = `<script>\\n${imp}\\n</script>\\n${content}`\n }\n return {code: content}\n },\n style: () => {},\n script: () => {},\n }\n}\n\n// List out the component names from ui/components\nlet cachedComponentNames: string[] | null = null\nexport function componentNames() {\n if (cachedComponentNames) return cachedComponentNames\n\n let files = fs.readdirSync(path.join(import.meta.dirname, '../ui/components'))\n cachedComponentNames = files.map(f => path.basename(f, '.svelte')).filter(f => !f.startsWith('_'))\n return cachedComponentNames || []\n}\n\nexport type PageFrontmatter = {title?: string}\n\n// Parse YAML frontmatter from the --- delimited block at the top of a markdown file.\nconst frontmatterRe = /^---\\s*\\n([\\s\\S]*?)\\n---(?:\\n|$)/\nexport function extractFrontmatter(contents: string): PageFrontmatter {\n let match = contents.trimStart().match(frontmatterRe)\n if (!match) return {}\n let raw = yaml.safeLoad(match[1]) as Record<string, any> | undefined\n return {title: raw?.title ? String(raw.title) : undefined}\n}\n\nexport const remarkPlugins: Array<Plugin> = [extractQueries, escapeAngles, mergeAdjacentHtml]\nexport const rehypePlugins: Array<Plugin> = [sanitizeMarkdown]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {svelte, vitePreprocess} from '@sveltejs/vite-plugin-svelte'\nimport crypto from 'crypto'\nimport fs from 'fs-extra'\n// import sveltePreprocess from 'svelte-preprocess' // this would be nice, but it breaks sourcemaps by default\nimport {type IncomingMessage, type ServerResponse} from 'http'\nimport {mdsvex} from 'mdsvex'\nimport {createRequire} from 'module'\nimport path from 'path'\nimport {fileURLToPath} from 'url'\nimport {createServer, type InlineConfig, optimizeDeps, resolveConfig, type ViteDevServer} from 'vite'\n\nimport type {AnalysisResult, WorkspaceFileInput} from '../lang/types.ts'\n\nimport {config} from '../lang/config.ts'\nimport {analyzeWorkspace, loadWorkspace, toSql} from '../lang/core.ts'\nimport {runQuery} from './connections/index.ts'\nimport {extractFrontmatter, injectComponentImports, remarkPlugins, rehypePlugins} from './mdCompile.ts'\nimport {mockFileMap} from './mockFiles.ts'\nimport {runVitePlugin} from './run.ts'\nimport {getWorkspaceScanCounts, type CliTelemetry} from './telemetry/index.ts'\n\n// Collect Svelte compiler warnings for test assertions\nexport type SvelteWarning = {code: string; message: string; filename?: string}\nexport const svelteWarnings: SvelteWarning[] = []\nexport function clearSvelteWarnings() {\n svelteWarnings.length = 0\n}\n\n// Bump this whenever the query response shape changes so client caches invalidate.\nconst QUERY_VERSION = 1\n\nlet uiRoot: string\nlet nodeRequire = createRequire(import.meta.url)\n\nexport async function serve2(telemetry?: CliTelemetry): Promise<ViteDevServer> {\n let server = await createServer(await createConfig(telemetry))\n // I originally added this to avoid the page refreshing immediately on load.\n // We def don't want to run it in tests, because its not safe to do in parallel.\n // I'm not sure it's still needed, now that we explicitly list out `optimizeDeps.includes`, refreshes should be rare\n // await optimizeDeps(server.config, true)\n await server.listen()\n console.log(`Server running at http://localhost:${server.config.server.port}`)\n\n return server\n}\n\nasync function createConfig(telemetry?: CliTelemetry): Promise<InlineConfig> {\n uiRoot = path.join(fileURLToPath(import.meta.url), '../../ui')\n let port = Number(process.env.GRAPHENE_PORT) || 4000\n let svelteRoot = path.dirname(nodeRequire.resolve('svelte/package.json'))\n let sveltePackage = nodeRequire('svelte/package.json')\n let svelteDependencyRoot = path.dirname(svelteRoot)\n let svelteExport = (name: string) => path.join(svelteRoot, sveltePackage.exports[name].browser || sveltePackage.exports[name].default)\n let packaged = path.basename(path.dirname(uiRoot)) == 'dist'\n await fs.ensureDir(path.resolve(config.root, 'node_modules/.graphene'))\n\n // Bind to 0.0.0.0 when running in a container so port forwarding works from the host\n let inContainer = fs.existsSync('/.dockerenv')\n let host = inContainer ? '0.0.0.0' : '127.0.0.1'\n\n return {\n root: config.root,\n logLevel: process.env.NODE_ENV == 'test' ? 'silent' : 'info',\n plugins: [\n svelte({\n configFile: false,\n extensions: ['.svelte', '.md'],\n preprocess: [\n vitePreprocess(),\n mdsvex({\n extensions: ['.md'],\n remarkPlugins,\n rehypePlugins,\n }) as any,\n injectComponentImports(),\n ],\n onwarn(warning, defaultHandler) {\n if (process.env.NODE_ENV === 'test') {\n svelteWarnings.push({code: warning.code, message: warning.message, filename: warning.filename})\n }\n defaultHandler?.(warning) // Still call the default handler to print warnings\n },\n }),\n fixSvelteDepsInTests(),\n fixHmrForFailedModules(),\n runVitePlugin(),\n handleRequestPlugin,\n updateWorkspacePlugin(telemetry),\n mockFilesForTests(),\n ],\n publicDir: path.resolve(uiRoot, 'public'),\n // on the fence about this one. This would make it less likely we need to optimize when alternating between dev and tests.\n // cacheDir: process.env.NODE_ENV == 'test' ? 'node_modules/.vite-tests' : 'node_modules/.vite',\n server: {\n port,\n host,\n fs: {strict: false},\n strictPort: true,\n hmr: {overlay: false}, // we handle compilation errors ourselves (see LocalApp.svelte)\n },\n resolve: {\n alias: [\n {find: /^graphene$/, replacement: path.resolve(uiRoot, 'web.js')},\n // Vite runs in a user project, but svelte is a direct dependency of the cli, and thus transitive to the user project.\n // So when Vite tries to resolve `svelte` from a compiled md page, it can't find it without these aliases.\n {find: /^svelte$/, replacement: svelteExport('.')},\n {find: /^svelte\\/animate$/, replacement: svelteExport('./animate')},\n {find: /^svelte\\/attachments$/, replacement: svelteExport('./attachments')},\n {find: /^svelte\\/easing$/, replacement: svelteExport('./easing')},\n {find: /^svelte\\/events$/, replacement: svelteExport('./events')},\n {find: /^svelte\\/internal$/, replacement: svelteExport('./internal')},\n {find: /^svelte\\/internal\\/client$/, replacement: svelteExport('./internal/client')},\n {find: /^svelte\\/internal\\/disclose-version$/, replacement: svelteExport('./internal/disclose-version')},\n {find: /^svelte\\/internal\\/flags\\/async$/, replacement: svelteExport('./internal/flags/async')},\n {find: /^svelte\\/internal\\/flags\\/legacy$/, replacement: svelteExport('./internal/flags/legacy')},\n {find: /^svelte\\/internal\\/flags\\/tracing$/, replacement: svelteExport('./internal/flags/tracing')},\n {find: /^svelte\\/legacy$/, replacement: svelteExport('./legacy')},\n {find: /^svelte\\/motion$/, replacement: svelteExport('./motion')},\n {find: /^svelte\\/reactivity$/, replacement: svelteExport('./reactivity')},\n {find: /^svelte\\/reactivity\\/window$/, replacement: svelteExport('./reactivity/window')},\n {find: /^svelte\\/store$/, replacement: svelteExport('./store')},\n {find: /^svelte\\/transition$/, replacement: svelteExport('./transition')},\n {find: /^clsx$/, replacement: path.join(svelteDependencyRoot, 'clsx/dist/clsx.mjs')},\n ],\n },\n\n optimizeDeps: {\n noDiscovery: process.env.NODE_ENV == 'test', // tests manually optimize before starting test workers\n exclude: ['virtual:nav'], // provided by a plugin, so don't try and optimize it\n // Vite running in a user project will not naturally discover and optimize these transitive deps.\n // When you launch the server, your first page load will automatically refresh after a second or two as Vite now sees and optimizes these.\n // This line makes it do that up-front, avoiding that reload jank. The packaged CLI also pre-bundles the `graphene` alias itself;\n // doing that from source causes trouble in examples/tests because the alias points outside node_modules.\n // `graphene` here is a special case: when packaged up it is considered a dependency, but in examples/tests, including it would cause errors.\n // oxfmt-ignore\n include: [\n ...(packaged ? ['graphene'] : []),\n '@graphenedata/cli > svelte',\n '@graphenedata/cli > chroma-js',\n '@graphenedata/cli > echarts',\n '@graphenedata/cli > @graphenedata/html2canvas',\n '@graphenedata/cli > @graphenedata/ui > svelte',\n '@graphenedata/cli > @graphenedata/ui > chroma-js',\n '@graphenedata/cli > @graphenedata/ui > echarts/dist/echarts.esm.js',\n '@graphenedata/cli > @graphenedata/ui > @graphenedata/html2canvas',\n ],\n },\n }\n}\n\nasync function handleQuery(req: IncomingMessage, res: ServerResponse<IncomingMessage>) {\n let chunks = [] as any[]\n for await (let chunk of req) chunks.push(chunk)\n let {gsql, params, hashes} = JSON.parse(Buffer.concat(chunks).toString())\n res.setHeader('Content-Type', 'application/json')\n\n await workspaceLoadPromise\n\n // queries should not analyze md files\n let gsqlFiles = workspaceFiles.filter(file => !file.path.endsWith('.md'))\n let result = analyzeWorkspace({config, files: [...gsqlFiles, {path: 'input', contents: gsql}]})\n updateParsedFiles(result)\n\n let diagnostics = result.diagnostics\n if (diagnostics.length) {\n res.statusCode = 400\n res.end(JSON.stringify(diagnostics[0]))\n return\n }\n\n let queries = result.files.find(file => file.path == 'input')?.queries || []\n if (queries.length > 1) throw new Error('Found multiple queries, which could be a parsing error')\n let sql = toSql(queries[0], params)\n\n // If the client already has this data, dont run the query\n let hash = crypto.createHash('SHA1').update(`query-v${QUERY_VERSION}|${sql}`).digest('hex')\n res.setHeader('ETag', hash)\n if (hashes.includes(hash) && req.headers['cache-control'] != 'no-cache') {\n res.statusCode = 304\n return res.end()\n }\n\n let queryResults = await runQuery(sql)\n let totalRows = queryResults.totalRows ?? queryResults.rows.length\n if (totalRows > queryResults.rows.length) throw new Error('Query returns too many rows')\n let fields = queries[0].fields.map(field => ({name: field.name, type: field.type, metadata: field.metadata || {}}))\n res.end(JSON.stringify({rows: queryResults.rows, hash, fields, sql}))\n}\n\nasync function handlePage(server: ViteDevServer, res: ServerResponse<IncomingMessage>) {\n res.setHeader('Content-Type', 'text/html')\n\n // Use a .html URL for transformIndexHtml so Vite doesn't run the svelte plugin on our HTML template.\n let html = await server.transformIndexHtml(\n '/index.html',\n `<!doctype html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Graphene</title>\n <link rel=\"icon\" href=\"/favicon.ico\" />\n </head>\n <body>\n <script type=\"module\">\n import 'graphene'\n </script>\n </body>\n </html>`,\n )\n return res.end(html)\n}\n\n// Runs vite's pre-bundling of dependencies. Used by tests to do this once, instead of for each worker.\nexport async function prepareDeps() {\n let cfg = await resolveConfig(await createConfig(), 'serve')\n await optimizeDeps(cfg, true)\n}\n\n// Svelte forces optimizeDeps whenever its own metadata has changed.\n// For tests, we already optimizeDeps before any tests start up, so we don't need this, and it causes problems\n// if multiple workers are all trying to optimizeDeps at the same time (vite isn't exactly concurrency-safe).\nfunction fixSvelteDepsInTests() {\n let viteConfig: any\n\n function configResolved(cfg: any) {\n viteConfig = cfg\n }\n\n // This must run AFTER Svelte's buildStart which sets force=true based on metadata changes.\n // Using enforce:'post' and sequential:true ensures we run last and can override.\n function buildStart() {\n if (process.env.NODE_ENV != 'test') return\n viteConfig.optimizeDeps.force = false\n }\n buildStart.sequential = true // force running after other sequential hooks (like svelte's)\n return {name: 'fix-svelte-deps', enforce: 'post' as const, configResolved, buildStart}\n}\n\n// When a module's transform fails (e.g. Svelte compilation error in an md file), Vite's import analysis\n// never runs on it, leaving `isSelfAccepting` as undefined. Vite's `propagateUpdate` silently skips\n// unanalyzed modules, so fixing the file produces no HMR update and the page stays broken.\n// We detect this and send a full-reload instead, since the module was never successfully loaded\n// and can't be hot-swapped.\nfunction fixHmrForFailedModules() {\n return {\n name: 'fix-hmr-for-failed-modules',\n hotUpdate(this: any, {modules}: {modules: any[]}) {\n // When a module's last transform failed, its transformResult is null. Vite's normal HMR can't\n // hot-swap a module that has no valid transform \u2014 either because it was never analyzed\n // (isSelfAccepting === undefined) or because it was previously working but is now broken.\n // In both cases, force a full page reload so the browser re-requests everything fresh.\n let hasFailed = modules.some(m => !m.transformResult)\n if (hasFailed) {\n this.environment.hot.send({type: 'full-reload', path: '*'})\n return []\n }\n },\n }\n}\n\n// Watch for changes to gsql files and reload the workspace.\n// This reload blocks all requests, so we shouldn't ever analyze without a workspace.\n// Also tracks all the md files in the workspace to populate the nav sidebar\nlet workspaceLoadPromise: Promise<void> | undefined\nlet workspaceFiles: WorkspaceFileInput[] = []\nlet mdFiles: {path: string; title?: string}[] = []\nfunction updateWorkspacePlugin(telemetry?: CliTelemetry) {\n return {\n name: 'updateWorkspace',\n resolveId(id: string) {\n if (id == 'virtual:nav') return '\\0virtual:nav'\n },\n load(id: string) {\n if (id != '\\0virtual:nav') return\n\n // in tests, inject mock files into the nav.\n // we do this on `load` as each test doesn't always refresh the workspace\n // TODO, we should prob inject these into `loadWorkspace`, then we wouldn't need this block at all\n let res = [...mdFiles]\n if (process.env.NODE_ENV == 'test') {\n for (let [path, contents] of Object.entries(mockFileMap)) {\n let mockFile = {path, title: extractFrontmatter(contents).title}\n let idx = res.findIndex(file => file.path == path)\n if (idx >= 0) res.splice(idx, 1, mockFile)\n else res.push(mockFile)\n }\n }\n\n return `export default ${JSON.stringify(res)}`\n },\n configureServer: (s: ViteDevServer) => {\n let refresh = async () => {\n workspaceLoadPromise = (async () => {\n let loaded = await loadWorkspace(config.root, true, config.ignoredFiles)\n telemetry?.event('workspace_scanned', {command: 'serve', ...getWorkspaceScanCounts(loaded)})\n workspaceFiles = loaded.map(file => {\n let existing = workspaceFiles.find(existing => existing.path == file.path && existing.contents == file.contents)\n return existing?.parsed ? {...file, parsed: existing.parsed} : file\n })\n })()\n await workspaceLoadPromise\n\n // store md file path/title so we can serve it as virtual:nav for the sidebar\n mdFiles = workspaceFiles.filter(file => file.path.endsWith('.md')).map(f => ({path: f.path, title: extractFrontmatter(f.contents).title}))\n\n let mod = s.moduleGraph.getModuleById('\\0virtual:nav')\n if (!mod) return\n s.reloadModule(mod) // triggers HMR of any `virtual:nav` imports\n }\n\n s.watcher.add(['**/*.gsql', '**/*.md'])\n s.watcher.on('all', refresh)\n refresh()\n },\n }\n}\n\nfunction updateParsedFiles(analysis: AnalysisResult) {\n workspaceFiles = workspaceFiles.map(file => {\n let analyzed = analysis.files.find(next => next.path == file.path)\n if (!analyzed) return file\n return {\n ...file,\n parsed: {\n tree: analyzed.tree!,\n virtualContents: analyzed.virtualContents,\n virtualToMarkdownOffset: analyzed.virtualToMarkdownOffset,\n },\n }\n })\n}\n\nconst handleRequestPlugin = {\n name: 'handleRequest',\n configureServer: (s: ViteDevServer) => {\n s.middlewares.use(async function handleRequest(req, res, next) {\n try {\n let [pathName] = (req.url || '').split('?')\n if (pathName == '/_api/query') return await handleQuery(req, res)\n if (pathName) if (pathName == '/__ct' || pathName == '/_charts' || pathName == '/_styles') return await handlePage(s, res)\n\n if (!pathName || pathName == '/') pathName = 'index'\n let relativeMdPath = pathName.replace(/^\\//, '') + '.md'\n let mdPath = path.join(config.root, relativeMdPath)\n\n if (mockFileMap[relativeMdPath] || (await fs.exists(mdPath))) {\n await handlePage(s, res)\n } else {\n next()\n }\n } catch (err: any) {\n if (process.env.NODE_ENV != 'test') console.error(err) // ignore in tests because they're noisy, and any unexpected errors should be captured by browserConsole.\n res.statusCode = 500\n res.end(JSON.stringify({message: err.message, stack: err.stack}))\n }\n })\n },\n}\n\nfunction mockFilesForTests() {\n if (process.env.NODE_ENV !== 'test') return null\n\n function toMockKey(id: string) {\n // Handle both absolute paths (/wt/.../index.md) and root-relative paths (/index.md)\n return id.replace(config.root + '/', '').replace(/^\\//, '')\n }\n\n return {\n name: 'mock-files-for-tests',\n enforce: 'pre' as const,\n resolveId(id: any) {\n if (!mockFileMap[toMockKey(id)]) return\n // Always resolve to the absolute path so the module graph key matches\n // what updateMockFile emits via server.watcher (needed for HMR to work).\n return path.join(config.root, toMockKey(id)) + '?mock'\n },\n load(id: any) {\n if (!id.endsWith('?mock')) return null\n return mockFileMap[toMockKey(id.replace(/\\?mock$/, ''))]\n },\n }\n}\n", "import type {Plugin} from 'unified'\n\nimport {decodeHTML} from 'entities'\nimport fs from 'fs'\nimport yaml from 'js-yaml'\nimport JSON5 from 'json5'\nimport path from 'path'\nimport sanitizeHtml from 'sanitize-html'\nimport {visit} from 'unist-util-visit'\n\n// Use JS escapes for HTML-sensitive chars so Svelte restores them before query registration.\nfunction svelteStringAttr(str: string) {\n let literal = str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/`/g, '\\\\`')\n .replace(/\\$\\{/g, '\\\\${')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\n/g, '\\\\n')\n .replace(/&/g, '\\\\u0026')\n .replace(/\"/g, '\\\\u0022')\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n return `{\\`${literal}\\`}`\n}\n\n// Takes the contents of a <ECharts> tag, and json5 parses it\nexport function liftInlineEChartsConfig(content: string) {\n return content.replace(/<ECharts\\b([^>]*)>([\\s\\S]*?)<\\/ECharts>/g, (match: string, attrs = '', body = '') => {\n let inline = body.trim()\n if (!inline) return match\n if (/\\sconfig\\s*=/.test(attrs)) return match\n let source = inline.startsWith('{') ? inline : `{${inline}}`\n let config = JSON.stringify(JSON5.parse(source), (_key, value) => (typeof value == 'string' ? decodeHTML(value) : value))\n return `<ECharts${attrs} config={${config}}></ECharts>`\n })\n}\n\n// Turn code fences into <GrapheneQuery> tags, which register those queries\nexport function extractQueries() {\n return function transformer(tree: any) {\n visit(tree, 'code', (node, index, parent) => {\n if (index === null) return\n let name = typeof node.meta === 'string' ? node.meta : ''\n let code = typeof node.value === 'string' ? node.value.trim() : ''\n parent.children[index] = {type: 'html', value: `<GrapheneQuery name=\"${svelteStringAttr(name)}\" code=\"${svelteStringAttr(code)}\" />`}\n })\n }\n}\n\n// remark will leave less-than and greater-than unescaped, which breaks svelte and prevents the page from loading.\nexport function escapeAngles() {\n return function transformer(tree: any) {\n visit(tree, 'text', (node: any) => {\n if (!node.value || typeof node.value !== 'string') return\n if (!node.value.includes('<')) return\n node.value = node.value.replace(/</g, '&lt;')\n })\n }\n}\n\n// remark can split one html block into adjacent html nodes when self-closing tags are involved.\n// Merge those sibling html nodes so downstream rehype/sanitize work on the full block.\nexport function mergeAdjacentHtml() {\n return function transformer(tree: any) {\n visit(tree, (parent: any) => {\n if (!Array.isArray(parent?.children)) return\n\n for (let i = 0; i < parent.children.length; i++) {\n if (parent.children[i]?.type !== 'html') continue\n\n let j = i\n while (j + 1 < parent.children.length && parent.children[j + 1]?.type === 'html') j++\n if (j == i) continue\n\n let value = parent.children\n .slice(i, j + 1)\n .map((node: any) => node.value || '')\n .join('\\n')\n parent.children.splice(i, j - i + 1, {type: 'html', value})\n }\n })\n }\n}\n\n// Restrict allowed components in markdown files to avoid xss issues.\n// This uses sanitize-html rather than rehype-sanitize because the latter had lots of issues with preserving tag casing,\n// as well as allowing all attributes on our allowlisted components.\nexport function sanitizeMarkdown() {\n return function transformer(tree: any) {\n visit(tree, 'raw', (node: any) => {\n if (typeof node.value !== 'string') return\n\n // sanitize-html doesn't like non-standard self-closing tags, so we need to rewrite them into open+close tags\n let expanded = node.value.replace(/<(\\w+)((?:\\s[^<>]*?)?)\\s*\\/>/gi, (_: string, name: string, attrs = '') => {\n let spacing = attrs\n return `<${name}${spacing}></${name}>`\n })\n\n let sanitized = sanitizeHtml(expanded, {\n ...sanitizeHtml.defaults,\n allowedTags: [...sanitizeHtml.defaults.allowedTags, ...componentNames()],\n allowedAttributes: {\n ...sanitizeHtml.defaults.allowedAttributes,\n ...Object.fromEntries(componentNames().map(n => [n, ['*']])),\n },\n parser: {\n ...((sanitizeHtml.defaults as any).parser || {}),\n lowerCaseAttributeNames: false,\n lowerCaseTags: false,\n },\n })\n node.value = sanitized\n })\n }\n}\n\n// We don't want users to have to manually import components in their md files, so we auto-import them.\nexport function injectComponentImports() {\n let imp = `const {${componentNames().join(', ')}} = window.$GRAPHENE.components`\n\n return {\n markup: ({content, filename}: {content: string; filename: string}) => {\n if (!filename.endsWith('.md')) return // only auto-import components for md files\n content = liftInlineEChartsConfig(content)\n if (content.includes('<script>')) {\n content = content.replace('<script>', `<script>\\n${imp}`)\n } else {\n content = `<script>\\n${imp}\\n</script>\\n${content}`\n }\n return {code: content}\n },\n style: () => {},\n script: () => {},\n }\n}\n\n// List out the component names from ui/components\nlet cachedComponentNames: string[] | null = null\nexport function componentNames() {\n if (cachedComponentNames) return cachedComponentNames\n\n let files = fs.readdirSync(path.join(import.meta.dirname, '../ui/components'))\n cachedComponentNames = files.map(f => path.basename(f, '.svelte')).filter(f => !f.startsWith('_'))\n return cachedComponentNames || []\n}\n\nexport type PageFrontmatter = {title?: string}\n\n// Parse YAML frontmatter from the --- delimited block at the top of a markdown file.\nconst frontmatterRe = /^---\\s*\\n([\\s\\S]*?)\\n---(?:\\n|$)/\nexport function extractFrontmatter(contents: string): PageFrontmatter {\n let match = contents.trimStart().match(frontmatterRe)\n if (!match) return {}\n let raw = yaml.safeLoad(match[1]) as Record<string, any> | undefined\n return {title: raw?.title ? String(raw.title) : undefined}\n}\n\nexport const remarkPlugins: Array<Plugin> = [extractQueries, escapeAngles, mergeAdjacentHtml]\nexport const rehypePlugins: Array<Plugin> = [sanitizeMarkdown]\n"],
5
+ "mappings": "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",
6
6
  "names": ["fs", "path", "config", "path", "fs", "existing"]
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  config
3
- } from "./chunk-QAXEOZ43.js";
3
+ } from "./chunk-SQVXTHE5.js";
4
4
 
5
5
  // connections/snowflake.ts
6
6
  import { createPrivateKey } from "node:crypto";
@@ -125,4 +125,4 @@ function snowflakeIdent(value) {
125
125
  export {
126
126
  SnowflakeConnection
127
127
  };
128
- //# sourceMappingURL=snowflake-MOQB5GA4.js.map
128
+ //# sourceMappingURL=snowflake-HVSTYBLB.js.map
package/dist/index.d.ts CHANGED
@@ -16,14 +16,14 @@ export type Field = {
16
16
  }
17
17
 
18
18
  // Metadata attached to fields.
19
- // There are a few built-in ones that Graphene already uses, but you can always attach your own metadata:
20
- // `price: cogs * 1.15 #ratio #format="US Dollar"` -> {ratio: true, format: 'US Dollar'}
19
+ // Graphene validates user-authored metadata annotations, while inferred metadata may add internal keys.
20
+ // `price: cogs * 1.15 #ratio #currency=USD` -> {ratio: true, currency: 'USD'}
21
21
  export type FieldMeta = {
22
22
  ratio?: true // 0 to 1 value
23
23
  pct?: true // 0 to 100 value
24
- units?: string
24
+ currency?: string // ISO 4217 currency code
25
+ unit?: string // physical unit label
25
26
  timeGrain?: TimeGrain // resolution when the field is a date or timestamp
26
- timePart?: string // extracted temporal part, normalized across backend spellings
27
27
  timeOrdinal?: TimeOrdinal // if the value represents something special like day_of_week, week_of_year, etc
28
28
  defaultName?: string // preferred output column name when an expression is selected without an alias
29
29
  [key: string]: string | true | undefined
@@ -16,14 +16,14 @@ export type Field = {
16
16
  }
17
17
 
18
18
  // Metadata attached to fields.
19
- // There are a few built-in ones that Graphene already uses, but you can always attach your own metadata:
20
- // `price: cogs * 1.15 #ratio #format="US Dollar"` -> {ratio: true, format: 'US Dollar'}
19
+ // Graphene validates user-authored metadata annotations, while inferred metadata may add internal keys.
20
+ // `price: cogs * 1.15 #ratio #currency=USD` -> {ratio: true, currency: 'USD'}
21
21
  export type FieldMeta = {
22
22
  ratio?: true // 0 to 1 value
23
23
  pct?: true // 0 to 100 value
24
- units?: string
24
+ currency?: string // ISO 4217 currency code
25
+ unit?: string // physical unit label
25
26
  timeGrain?: TimeGrain // resolution when the field is a date or timestamp
26
- timePart?: string // extracted temporal part, normalized across backend spellings
27
27
  timeOrdinal?: TimeOrdinal // if the value represents something special like day_of_week, week_of_year, etc
28
28
  defaultName?: string // preferred output column name when an expression is selected without an alias
29
29
  [key: string]: string | true | undefined
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: graphene
3
3
  description: How to use Graphene, our framework for data modeling, analysis, and visualization.
4
+ allowed-tools: Bash(npx graphene:*) Bash(pnpm graphene:*) Bash(yarn graphene:*) Bash(bun run graphene:*)
4
5
  ---
5
6
 
6
7
  Graphene is a framework for doing data analysis and BI as code. Schema definitions and semantic models are in `.gsql` files, dashboards/notebooks (called pages) in `.md`.
@@ -13,12 +14,12 @@ table orders (
13
14
  id bigint
14
15
  created_at datetime
15
16
  user_id bigint
16
- amount float #units=usd
17
+ amount float #currency=USD
17
18
  status string
18
19
  join one users on user_id = users.id -- many orders per user
19
20
  is_complete: status = 'Complete' -- dimension (scalar expression)
20
- revenue: sum(amount) -- measure (agg expression) #units=usd
21
- avg_order: revenue / count(*) -- measures can compose #units=usd
21
+ revenue: sum(amount) -- measure (agg expression) #currency=USD
22
+ avg_order: revenue / count(*) -- measures can compose #currency=USD
22
23
  )
23
24
  table users (
24
25
  id bigint
@@ -170,9 +171,11 @@ graphene check path/to/file.gsql # Check diagnostics for one specific gsql file
170
171
  graphene check path/to/page.md # Check diagnostics for one specific markdown file
171
172
 
172
173
  graphene run "from flights select count() as total" # Run inline GSQL and print results
174
+ graphene run 'from flights where carrier = $carrier select count() as total' --input carrier=AA # Provide parameter input values
173
175
  graphene run - # Read GSQL from stdin and print results
174
176
 
175
177
  graphene run path/to/page.md # Run the page and save a full-page screenshot
178
+ graphene run path/to/page.md --input carrier=AA # Run the page with input values, overriding page defaults
176
179
 
177
180
  # `-c/--chart` can target either a chart title or the chart's `queryId`. For charts without titles use `graphene list` to see the exact IDs for charts on a page.
178
181
  graphene run path/to/page.md -c "Chart Title" # Run the page and screenshot one chart by title
@@ -180,6 +183,10 @@ graphene run path/to/page.md -c 'Query (data="query_name" x="category" y="total"
180
183
 
181
184
  # `-q/--query` can target anything usable in a chart `data` prop (for example, a gsql table or a named code-fenced query in the markdown file).
182
185
  graphene run path/to/page.md -q query_name # Run a named query/table from the markdown context and print results
186
+ graphene run path/to/page.md -q query_name --input carrier=AA # Run a parameterized named query/table
187
+
188
+ # `--input` values are strings. Repeat the flag to pass multiple values for one input.
189
+ graphene run path/to/page.md --input carrier=AA --input carrier=DL
183
190
 
184
191
  graphene compile "[GSQL]" # Show the compiled, dialect-specific SQL
185
192
 
@@ -17,8 +17,8 @@ table orders (
17
17
  user_id BIGINT
18
18
  created_at DATETIME
19
19
  status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
20
- amount FLOAT -- Amount paid by customer #units=usd
21
- cost FLOAT -- Cost of materials #units=usd
20
+ amount FLOAT -- Amount paid by customer #currency=USD
21
+ cost FLOAT -- Cost of materials #currency=USD
22
22
 
23
23
  -- Join relationships
24
24
 
@@ -30,9 +30,9 @@ table orders (
30
30
 
31
31
  -- Measures
32
32
 
33
- revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
34
- cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
35
- profit: revenue - cogs #units=usd
33
+ revenue: sum(case when revenue_recognized then amount else 0 end) #currency=USD
34
+ cogs: sum(case when revenue_recognized then cost else 0 end) #currency=USD
35
+ profit: revenue - cogs #currency=USD
36
36
  profit_margin: profit / revenue #ratio
37
37
  )
38
38
 
@@ -123,9 +123,9 @@ table orders (
123
123
  revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
124
124
 
125
125
  /* Agg expressions */
126
- revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
127
- cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
128
- profit: revenue - cogs #units=usd -- even though there are no agg functions here, this is still aggregative as it references other aggregative expressions
126
+ revenue: sum(case when revenue_recognized then amount else 0 end) #currency=USD
127
+ cogs: sum(case when revenue_recognized then cost else 0 end) #currency=USD
128
+ profit: revenue - cogs #currency=USD -- even though there are no agg functions here, this is still aggregative as it references other aggregative expressions
129
129
  profit_margin: profit / revenue #ratio
130
130
  )
131
131
  ```
@@ -138,7 +138,7 @@ There isn't always a SQL expression that can tip Graphene to the semantic meanin
138
138
  - The field could be a base column that has no source expression
139
139
  - There might not be enough information in the expression (eg. what currency a float is tied to)
140
140
 
141
- For this reason, some metadata should be set explicitly in the GSQL model, using annotations. Metadata annotations resemble hashtags (eg. `#ratio`, `#units=usd`) that can be inlined or written above the object they decorate.
141
+ For this reason, some metadata should be set explicitly in the GSQL model, using annotations. Metadata annotations resemble hashtags (eg. `#ratio`, `#currency=USD`) that can be inlined or written above the object they decorate.
142
142
 
143
143
  #### Recognized metadata
144
144
 
@@ -146,9 +146,12 @@ For this reason, some metadata should be set explicitly in the GSQL model, using
146
146
  |---|---|---|
147
147
  | `#ratio` | no | Value is 0–1; rendered as `value × 100%` (e.g. `0.42` → `42%`) |
148
148
  | `#pct` | no | Value is already 0–100; rendered as `value%` (e.g. `42` → `42%`) |
149
- | `#units=<currency>` | no | Adds currency symbol and compacts to K/M/B. Accepted values: `usd`, `eur`, `gbp`, `cad`, `aud`, `jpy` |
149
+ | `#currency=<code>` | no | Adds currency symbol and compacts to K/M/B. Accepts ISO 4217 currency codes like `USD`, `EUR`, and `JPY` |
150
+ | `#unit=<unit>` | no | Appends the provided value to the end of labels in visualizations (e.g. `unit=minutes` appends "minutes", or "(minutes)" on axes). Any non-empty value is accepted |
150
151
  | `#timeGrain=<grain>` | yes (from `date_trunc`, `date_bin`, casts) | Controls time axis label format. Values: `year`, `quarter`, `month`, `week`, `day`, `hour`, `minute`, `second` |
151
- | `#timeOrdinal=<ordinal>` | yes (from `extract`) | Treats values as ordinal positions on a categorical axis. Values: `hour_of_day`, `day_of_month`, `day_of_year`, `week_of_year`, `month_of_year`, `quarter_of_year`, `dow_0s` (0=Sun), `dow_1s` (1=Sun), `dow_1m` (1=Mon) |
152
+ | `#timeOrdinal=<ordinal>` | yes (from `extract`) | Treats extracted time values as ordered positions. Values: `hour_of_day`, `day_of_month`, `day_of_year`, `week_of_year`, `month_of_year`, `quarter_of_year`, `dow_0s` (0=Sun), `dow_1s` (1=Sun), `dow_1m` (1=Mon) |
153
+ | `#description=<text>` | no | Description text for a table or field. `--` comments are also collected as descriptions |
154
+ | `#pii` | no | Marks a field as containing personally identifiable information. |
152
155
 
153
156
  ## `select` statements
154
157
 
@@ -241,15 +244,15 @@ table orders (
241
244
  user_id BIGINT
242
245
  created_at DATETIME
243
246
  status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
244
- amount FLOAT -- Amount paid by customer #units=usd
245
- cost FLOAT -- Cost of materials #units=usd
247
+ amount FLOAT -- Amount paid by customer #currency=USD
248
+ cost FLOAT -- Cost of materials #currency=USD
246
249
 
247
250
  join one users on user_id = users.id
248
251
 
249
252
  revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
250
- revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
251
- cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
252
- profit: revenue - cogs #units=usd
253
+ revenue: sum(case when revenue_recognized then amount else 0 end) #currency=USD
254
+ cogs: sum(case when revenue_recognized then cost else 0 end) #currency=USD
255
+ profit: revenue - cogs #currency=USD
253
256
  profit_margin: profit / revenue #ratio
254
257
  )
255
258
  ```
@@ -330,15 +333,15 @@ table orders (
330
333
  user_id BIGINT
331
334
  created_at DATETIME
332
335
  status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
333
- amount FLOAT -- Amount paid by customer #units=usd
334
- cost FLOAT -- Cost of materials #units=usd
336
+ amount FLOAT -- Amount paid by customer #currency=USD
337
+ cost FLOAT -- Cost of materials #currency=USD
335
338
 
336
339
  join one users on user_id = users.id
337
340
 
338
341
  revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
339
- revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
340
- cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
341
- profit: revenue - cogs #units=usd
342
+ revenue: sum(case when revenue_recognized then amount else 0 end) #currency=USD
343
+ cogs: sum(case when revenue_recognized then cost else 0 end) #currency=USD
344
+ profit: revenue - cogs #currency=USD
342
345
  profit_margin: profit / revenue #ratio
343
346
  )
344
347
 
@@ -351,7 +354,7 @@ table users (
351
354
  join many orders on id = orders.user_id
352
355
  join one user_facts on id = user_facts.id
353
356
 
354
- ltv: user_facts.ltv #units=usd
357
+ ltv: user_facts.ltv #currency=USD
355
358
  lifetime_orders: user_facts.lifetime_orders
356
359
  )
357
360
 
@@ -388,6 +391,6 @@ We can extend this table to add measures or joins:
388
391
  extend regional_orders (
389
392
  join one regions on region = regions.name
390
393
 
391
- avg_order_value: total_revenue / num_orders #units=usd
394
+ avg_order_value: total_revenue / num_orders #currency=USD
392
395
  )
393
396
  ```