yamchart 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{advisor-CXXGXZ5V.js → advisor-IJG6UFTJ.js} +15 -7
- package/dist/advisor-IJG6UFTJ.js.map +1 -0
- package/dist/{chunk-ZN3AJM76.js → chunk-EHM6AMMA.js} +78 -2
- package/dist/chunk-EHM6AMMA.js.map +1 -0
- package/dist/chunk-WYS4ULBE.js +125 -0
- package/dist/chunk-WYS4ULBE.js.map +1 -0
- package/dist/{describe-Y4VD4JQX.js → describe-HD2RJQX3.js} +2 -2
- package/dist/{dev-7SWSX2X7.js → dev-LCYHM7UA.js} +19 -3
- package/dist/dev-LCYHM7UA.js.map +1 -0
- package/dist/{dist-ZRRM3OWF.js → dist-D5NLPC5X.js} +62 -1
- package/dist/dist-D5NLPC5X.js.map +1 -0
- package/dist/index.js +49 -6
- package/dist/index.js.map +1 -1
- package/dist/lineage-TX33DUDP.js +81 -0
- package/dist/lineage-TX33DUDP.js.map +1 -0
- package/dist/public/assets/{LoginPage-BhzP7Hkq.js → LoginPage-NNsbTBPo.js} +1 -1
- package/dist/public/assets/{PublicViewer-owhT_M21.js → PublicViewer-DDsvIFas.js} +1 -1
- package/dist/public/assets/{SetupWizard-BbV-9haQ.js → SetupWizard-CY-o51--.js} +1 -1
- package/dist/public/assets/{ShareManagement-BWDdV-hd.js → ShareManagement-B-Kz0tT8.js} +1 -1
- package/dist/public/assets/{UserManagement-DGGk6XAr.js → UserManagement-B6LjBcOb.js} +1 -1
- package/dist/public/assets/{index-Ce_q4or4.js → index-IDI-DDZi.js} +11 -11
- package/dist/public/assets/{index.es-DZ9bSPjd.js → index.es-DK8yr16j.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-BZhCusuH.js → jspdf.es.min-CDLoTXR9.js} +3 -3
- package/dist/public/index.html +1 -1
- package/dist/{search-CXBNM2KK.js → search-ECGHSZBU.js} +2 -2
- package/dist/{sync-dbt-HICRXDB2.js → sync-dbt-RKEHTN4P.js} +35 -54
- package/dist/sync-dbt-RKEHTN4P.js.map +1 -0
- package/dist/sync-warehouse-WPNGI6YP.js +367 -0
- package/dist/sync-warehouse-WPNGI6YP.js.map +1 -0
- package/dist/{tables-ZY3NRC6E.js → tables-5BVEKUPB.js} +2 -2
- package/dist/templates/default/CLAUDE.md +2 -0
- package/dist/templates/default/docs/yamchart-reference.md +54 -0
- package/package.json +4 -4
- package/dist/advisor-CXXGXZ5V.js.map +0 -1
- package/dist/chunk-ZN3AJM76.js.map +0 -1
- package/dist/dev-7SWSX2X7.js.map +0 -1
- package/dist/dist-ZRRM3OWF.js.map +0 -1
- package/dist/sync-dbt-HICRXDB2.js.map +0 -1
- /package/dist/{describe-Y4VD4JQX.js.map → describe-HD2RJQX3.js.map} +0 -0
- /package/dist/{search-CXBNM2KK.js.map → search-ECGHSZBU.js.map} +0 -0
- /package/dist/{tables-ZY3NRC6E.js.map → tables-5BVEKUPB.js.map} +0 -0
|
@@ -57,6 +57,16 @@ yamchart advisor "question" # Single question mode
|
|
|
57
57
|
yamchart advisor audit # Comprehensive dbt project audit
|
|
58
58
|
yamchart advisor audit --top 3 # Limit audit suggestions
|
|
59
59
|
yamchart advisor --dbt-path ../dbt # Override dbt project path
|
|
60
|
+
yamchart sync-warehouse # Sync warehouse table metadata into catalog
|
|
61
|
+
yamchart sync-warehouse --schema RAW,STAGING # Only specific schemas
|
|
62
|
+
yamchart sync-warehouse -c snowflake # Override connection
|
|
63
|
+
yamchart sync-warehouse --skip-samples # Metadata only, no sample rows
|
|
64
|
+
yamchart sync-warehouse --refresh # Re-run with saved connection/schema config
|
|
65
|
+
yamchart sync-warehouse --full # Force full re-sync (ignore incremental state)
|
|
66
|
+
yamchart sync-warehouse --json # Output sync summary as JSON
|
|
67
|
+
yamchart lineage <model> # Show upstream dependency tree
|
|
68
|
+
yamchart lineage <model> --depth 2 # Limit recursion depth
|
|
69
|
+
yamchart lineage <model> --json # JSON output
|
|
60
70
|
```
|
|
61
71
|
|
|
62
72
|
## SQL Models (`models/*.sql`)
|
|
@@ -547,6 +557,8 @@ path: ./data/app.db
|
|
|
547
557
|
|
|
548
558
|
Use `${ENV_VAR}` for credentials. Never commit secrets.
|
|
549
559
|
|
|
560
|
+
**Snowflake SSO reconnect:** When using `externalbrowser` auth, the connection indicator in the top bar monitors connection health. If the Snowflake session expires, the indicator turns red and a "Re-authenticate" button appears. Clicking it triggers a new browser SSO popup without restarting the server. All charts auto-refresh after successful re-authentication.
|
|
561
|
+
|
|
550
562
|
## Schedules (`schedules/*.yaml`)
|
|
551
563
|
|
|
552
564
|
### Scheduled Report
|
|
@@ -685,6 +697,48 @@ Dashboard text widgets support AI-powered text generation via slash commands in
|
|
|
685
697
|
- `/ai! create a quarterly business review with all KPI highlights`
|
|
686
698
|
- `/ai add a bullet list of key trends`
|
|
687
699
|
|
|
700
|
+
## Warehouse Metadata Sync
|
|
701
|
+
|
|
702
|
+
Pulls table metadata and sample rows directly from your warehouse into the catalog. Tables landed by ELT tools (Fivetran, Hevo, Rudderstack, Airbyte, etc.) become visible to the advisor, `ref()` resolution, and CLI commands like `describe` and `sample`.
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
yamchart sync-warehouse # Sync all schemas from default connection
|
|
706
|
+
yamchart sync-warehouse --schema RAW,STAGING # Only specific schemas
|
|
707
|
+
yamchart sync-warehouse -c snowflake # Override connection
|
|
708
|
+
yamchart sync-warehouse --skip-samples # Metadata only, no sample rows
|
|
709
|
+
yamchart sync-warehouse --refresh # Re-run with saved connection/schema config
|
|
710
|
+
yamchart sync-warehouse --full # Force full re-sync (ignore incremental state)
|
|
711
|
+
yamchart sync-warehouse --json # JSON output
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Incremental sync:** Subsequent runs only re-sample tables whose columns have changed. Sync state is saved in `.yamchart/warehouse-sync.json`.
|
|
715
|
+
|
|
716
|
+
**Three-tier catalog:**
|
|
717
|
+
1. `.yamchart/catalog.md` — compact grouped index for LLM context (~500 tokens regardless of warehouse size)
|
|
718
|
+
2. `.yamchart/catalog.json` — full detail (columns, types, sample rows) for machine use
|
|
719
|
+
3. Advisor `get_warehouse_table` tool — on-demand detail from cached catalog, no live query
|
|
720
|
+
|
|
721
|
+
## Lineage
|
|
722
|
+
|
|
723
|
+
Traces upstream dependencies for any model in the catalog. Reads from `.yamchart/catalog.json` (populated by `sync-dbt`). Shows dbt models, dbt sources, and the full ancestry tree.
|
|
724
|
+
|
|
725
|
+
```bash
|
|
726
|
+
yamchart lineage fct_revenue # Show upstream dependencies
|
|
727
|
+
yamchart lineage fct_revenue --depth 2 # Limit recursion depth
|
|
728
|
+
yamchart lineage fct_revenue --json # JSON output
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
Example output:
|
|
732
|
+
```
|
|
733
|
+
fct_revenue
|
|
734
|
+
← stg_orders
|
|
735
|
+
← source: raw.orders
|
|
736
|
+
← stg_payments
|
|
737
|
+
← source: raw.payments
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
**Note:** Lineage data requires `sync-dbt` with a dbt project that has run `dbt compile` or `dbt run` (which generates `target/manifest.json`). Source nodes are automatically extracted and included in the catalog.
|
|
741
|
+
|
|
688
742
|
## Axis Types
|
|
689
743
|
|
|
690
744
|
| Type | Use | Example |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yamchart",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Git-native business intelligence dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
"tsup": "^8.0.0",
|
|
65
65
|
"typescript": "^5.7.0",
|
|
66
66
|
"vitest": "^2.1.0",
|
|
67
|
-
"@yamchart/advisor": "0.1.0",
|
|
68
67
|
"@yamchart/auth-local": "0.1.0",
|
|
69
68
|
"@yamchart/config": "0.1.2",
|
|
70
69
|
"@yamchart/query": "0.1.2",
|
|
71
|
-
"@yamchart/
|
|
72
|
-
"@yamchart/
|
|
70
|
+
"@yamchart/server": "0.1.2",
|
|
71
|
+
"@yamchart/advisor": "0.1.0",
|
|
72
|
+
"@yamchart/schema": "0.1.2"
|
|
73
73
|
},
|
|
74
74
|
"files": [
|
|
75
75
|
"dist",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/advisor.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport { createInterface } from 'readline';\nimport pc from 'picocolors';\nimport * as output from '../utils/output.js';\nimport { resolveConnection, createConnector } from './connection-utils.js';\nimport type { Message, AdvisorContext, Proposal } from '@yamchart/advisor';\n\nexport interface AdvisorOptions {\n top?: number;\n json?: boolean;\n dbtPath?: string;\n connection?: string;\n}\n\nasync function loadCatalogMarkdown(projectDir: string): Promise<string | null> {\n try {\n const catalogPath = join(projectDir, '.yamchart', 'catalog.md');\n await access(catalogPath);\n return readFile(catalogPath, 'utf-8');\n } catch {\n return null;\n }\n}\n\nasync function getDbtProjectPath(projectDir: string, override?: string): Promise<string | null> {\n if (override) return override;\n\n try {\n const dbtSourcePath = join(projectDir, '.yamchart', 'dbt-source.yaml');\n const content = await readFile(dbtSourcePath, 'utf-8');\n const parsed = parseYaml(content) as { path?: string };\n if (parsed.path) {\n return join(projectDir, parsed.path);\n }\n } catch {\n // No saved dbt source config\n }\n\n return null;\n}\n\nasync function buildContext(\n projectDir: string,\n options: AdvisorOptions,\n): Promise<AdvisorContext> {\n // 1. Load yamchart models\n const { parseModelMetadata } = await import('@yamchart/query');\n const modelsDir = join(projectDir, 'models');\n const yamchartModels: AdvisorContext['yamchart']['models'] = [];\n\n try {\n const files = await readdir(modelsDir, { recursive: true });\n for (const file of files) {\n if (!String(file).endsWith('.sql')) continue;\n const content = await readFile(join(modelsDir, String(file)), 'utf-8');\n try {\n const metadata = parseModelMetadata(content);\n yamchartModels.push({\n name: metadata.name,\n description: metadata.description,\n sql: metadata.sql,\n params: metadata.params?.map((p) => ({ name: p.name, type: p.type })),\n returns: metadata.returns?.map((r) => ({ name: r.name, type: r.type })),\n });\n } catch {\n // Skip unparseable models\n }\n }\n } catch {\n // No models dir\n }\n\n // 2. Load yamchart charts\n const chartsDir = join(projectDir, 'charts');\n const yamchartCharts: AdvisorContext['yamchart']['charts'] = [];\n\n try {\n const files = await readdir(chartsDir, { recursive: true });\n for (const file of files) {\n const fname = String(file);\n if (!fname.endsWith('.yaml') && !fname.endsWith('.yml')) continue;\n const content = await readFile(join(chartsDir, fname), 'utf-8');\n try {\n const parsed = parseYaml(content) as {\n name?: string;\n title?: string;\n source?: { model?: string };\n chart?: { type?: string };\n };\n if (parsed.name && parsed.source?.model) {\n yamchartCharts.push({\n name: parsed.name,\n title: parsed.title,\n model: parsed.source.model,\n type: parsed.chart?.type ?? 'unknown',\n });\n }\n } catch {\n // Skip unparseable charts\n }\n }\n } catch {\n // No charts dir\n }\n\n // 3. Load yamchart dashboards\n const dashboardsDir = join(projectDir, 'dashboards');\n const yamchartDashboards: AdvisorContext['yamchart']['dashboards'] = [];\n\n try {\n const files = await readdir(dashboardsDir, { recursive: true });\n for (const file of files) {\n const fname = String(file);\n if (!fname.endsWith('.yaml') && !fname.endsWith('.yml')) continue;\n const content = await readFile(join(dashboardsDir, fname), 'utf-8');\n try {\n const parsed = parseYaml(content) as {\n name?: string;\n title?: string;\n layout?: { rows?: Array<{ widgets?: Array<{ ref?: string }> }> };\n };\n if (parsed.name) {\n const charts: string[] = [];\n for (const row of parsed.layout?.rows ?? []) {\n for (const widget of row.widgets ?? []) {\n if (widget.ref) charts.push(widget.ref);\n }\n }\n yamchartDashboards.push({\n name: parsed.name,\n title: parsed.title,\n charts,\n });\n }\n } catch {\n // Skip\n }\n }\n } catch {\n // No dashboards dir\n }\n\n // 4. Load catalog\n const catalog = await loadCatalogMarkdown(projectDir);\n\n // 5. Load dbt project\n const dbtPath = await getDbtProjectPath(projectDir, options.dbtPath);\n let dbt: AdvisorContext['dbt'] = {\n projectPath: dbtPath ?? '',\n projectName: 'unknown',\n conventions: {\n folderStructure: [],\n namingPrefixes: {},\n commonMaterializations: {},\n schemaYmlPattern: 'per-folder',\n testPatterns: [],\n },\n models: [],\n };\n\n if (dbtPath) {\n try {\n const { detectConventions, listDbtModels } = await import('@yamchart/advisor');\n const dbtProjectYml = await readFile(join(dbtPath, 'dbt_project.yml'), 'utf-8');\n const dbtConfig = parseYaml(dbtProjectYml) as { name?: string };\n\n const conventions = await detectConventions(dbtPath);\n const models = await listDbtModels(dbtPath);\n\n dbt = {\n projectPath: dbtPath,\n projectName: dbtConfig.name ?? 'unknown',\n conventions,\n models,\n };\n } catch (err) {\n output.warning(\n `Could not read dbt project: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // 6. Set up warehouse connection (optional)\n let warehouse: AdvisorContext['warehouse'] = null;\n\n try {\n const connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\n await connector.connect();\n\n warehouse = {\n connectionType: connection.type,\n executeSql: async (sql: string) => {\n const result = await connector.execute(sql);\n return {\n columns: result.columns.map((c) => c.name),\n rows: result.rows,\n };\n },\n };\n } catch {\n // No connection available — advisor works without it\n }\n\n return {\n yamchart: {\n models: yamchartModels,\n charts: yamchartCharts,\n dashboards: yamchartDashboards,\n catalog,\n },\n dbt,\n warehouse,\n };\n}\n\nfunction formatProposal(\n proposal: Proposal,\n conventions: AdvisorContext['dbt']['conventions'],\n buildModelPathFn: (\n name: string,\n layer: string | undefined,\n conventions: AdvisorContext['dbt']['conventions'],\n subfolder?: string,\n ) => string,\n): string {\n const path = buildModelPathFn(\n proposal.name,\n proposal.layer,\n conventions,\n proposal.subfolder,\n );\n\n const lines = [\n pc.bold(` ${proposal.name}`),\n ` ${pc.dim(proposal.explanation)}`,\n ` Path: ${pc.cyan(path)} (${proposal.materialization})`,\n '',\n pc.dim(' ' + '\\u2500'.repeat(30)),\n proposal.sql\n .split('\\n')\n .map((l: string) => ` ${pc.dim(l)}`)\n .join('\\n'),\n pc.dim(' ' + '\\u2500'.repeat(30)),\n ];\n return lines.join('\\n');\n}\n\nexport async function runAdvisor(\n projectDir: string,\n questionOrMode: string | undefined,\n options: AdvisorOptions,\n): Promise<void> {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n output.error('ANTHROPIC_API_KEY environment variable is required');\n output.detail('Set it in your .env file or shell environment');\n process.exit(1);\n }\n\n const spin = output.spinner('Loading project context...');\n const context = await buildContext(projectDir, options);\n spin.stop();\n\n // Print startup banner\n console.log('');\n output.header('dbt advisor');\n if (context.dbt.projectPath) {\n output.detail(\n `dbt project: ${context.dbt.projectName} at ${context.dbt.projectPath}`,\n );\n output.detail(\n `dbt models: ${context.dbt.models.length} (layers: ${context.dbt.conventions.folderStructure.join(', ') || 'none'})`,\n );\n } else {\n output.warning(\n 'No dbt project found. Run `yamchart sync-dbt --path <dbt-project>` to connect one.',\n );\n }\n output.detail(\n `yamchart: ${context.yamchart.models.length} models, ${context.yamchart.charts.length} charts, ${context.yamchart.dashboards.length} dashboards`,\n );\n output.detail(`catalog: ${context.yamchart.catalog ? 'loaded' : 'not synced'}`);\n output.detail(\n `warehouse: ${context.warehouse ? `connected (${context.warehouse.connectionType})` : 'not connected'}`,\n );\n console.log('');\n\n // Create agent\n const { AdvisorAgent, AnthropicProvider, buildModelPath } = await import(\n '@yamchart/advisor'\n );\n const provider = new AnthropicProvider(apiKey);\n const agent = new AdvisorAgent(provider);\n\n const isAudit = questionOrMode === 'audit';\n\n if (isAudit) {\n // One-shot audit mode\n const top = options.top ?? 5;\n const auditPrompt = `Run a comprehensive audit of this project. Return your top ${top} suggestions ranked by impact. For each, call propose_model with the full SQL.`;\n\n const auditSpin = output.spinner('Running audit...');\n const result = await agent.run(context, [\n { role: 'user', content: auditPrompt },\n ]);\n auditSpin.stop();\n\n if (options.json) {\n console.log(\n JSON.stringify(\n { text: result.response, proposals: result.proposals },\n null,\n 2,\n ),\n );\n return;\n }\n\n console.log(result.response);\n\n if (result.proposals.length > 0) {\n console.log('');\n output.header(`${result.proposals.length} suggestion(s):`);\n\n for (let i = 0; i < result.proposals.length; i++) {\n const proposal = result.proposals[i]!;\n console.log(\n `\\n${pc.bold(`[${i + 1}]`)} ${formatProposal(proposal, context.dbt.conventions, buildModelPath)}`,\n );\n }\n\n // Offer to apply\n await offerApply(context, result.proposals);\n }\n return;\n }\n\n // Interactive mode\n if (questionOrMode) {\n // Single question mode\n const spin2 = output.spinner('Thinking...');\n const result = await agent.run(context, [\n { role: 'user', content: questionOrMode },\n ]);\n spin2.stop();\n\n console.log(result.response);\n\n if (result.proposals.length > 0) {\n const { buildModelPath: bmp } = await import('@yamchart/advisor');\n for (const proposal of result.proposals) {\n console.log(\n `\\n${formatProposal(proposal, context.dbt.conventions, bmp)}`,\n );\n }\n await offerApply(context, result.proposals);\n }\n return;\n }\n\n // Multi-turn interactive mode\n console.log(\n pc.dim(\n ' Ask me anything, or type \"audit\" for a full analysis. Type \"exit\" to quit.\\n',\n ),\n );\n\n const conversationHistory: Message[] = [];\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const prompt = (): Promise<string> =>\n new Promise((resolve) => rl.question(pc.green('> '), resolve));\n\n while (true) {\n const userInput = await prompt();\n if (!userInput.trim()) continue;\n if (userInput.trim().toLowerCase() === 'exit') {\n rl.close();\n break;\n }\n\n if (userInput.trim().toLowerCase() === 'audit') {\n conversationHistory.push({\n role: 'user',\n content: `Run a comprehensive audit. Return your top ${options.top ?? 5} suggestions ranked by impact.`,\n });\n } else {\n conversationHistory.push({ role: 'user', content: userInput });\n }\n\n const spin3 = output.spinner('Thinking...');\n const result = await agent.run(context, conversationHistory);\n spin3.stop();\n\n console.log('\\n' + result.response);\n\n // Update conversation history with the full exchange\n conversationHistory.length = 0;\n conversationHistory.push(...result.messages);\n\n if (result.proposals.length > 0) {\n const { buildModelPath: bmp } = await import('@yamchart/advisor');\n for (const proposal of result.proposals) {\n console.log(\n `\\n${formatProposal(proposal, context.dbt.conventions, bmp)}`,\n );\n }\n await offerApply(context, result.proposals);\n }\n\n console.log('');\n }\n}\n\nasync function offerApply(\n context: AdvisorContext,\n proposals: Proposal[],\n): Promise<void> {\n if (!context.dbt.projectPath) {\n output.warning('No dbt project path configured \\u2014 cannot write files');\n return;\n }\n\n const { confirm } = await import('@inquirer/prompts');\n const { writeDbtModel, buildModelPath, formatModelSql, updateSchemaYml } =\n await import('@yamchart/advisor');\n\n for (const proposal of proposals) {\n console.log('');\n const shouldApply = await confirm({\n message: `Apply ${proposal.name} to dbt project?`,\n default: false,\n });\n\n if (!shouldApply) continue;\n\n const modelPath = buildModelPath(\n proposal.name,\n proposal.layer,\n context.dbt.conventions,\n proposal.subfolder,\n );\n\n const sql = formatModelSql({\n materialization: proposal.materialization,\n sql: proposal.sql,\n });\n\n await writeDbtModel(context.dbt.projectPath, modelPath, sql);\n output.success(`Created ${modelPath}`);\n\n // Update schema.yml\n const schemaDir = modelPath.split('/').slice(0, -1).join('/');\n const schemaPath = `${schemaDir}/schema.yml`;\n\n try {\n await updateSchemaYml(context.dbt.projectPath, schemaPath, {\n name: proposal.name,\n description: proposal.description,\n columns: proposal.columns,\n });\n output.success(`Updated ${schemaPath}`);\n } catch (err) {\n output.warning(\n `Could not update schema.yml: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/introspection.ts"],"sourcesContent":["/**\n * Per-engine SQL generation and result normalization for database introspection.\n *\n * Pure logic — no I/O, no connectors. Generates SQL strings and provides\n * normalize functions to transform raw query results into consistent shapes.\n */\n\nexport interface NormalizedTable {\n schema: string;\n name: string;\n type: string;\n}\n\nexport interface NormalizedColumn {\n name: string;\n type: string;\n nullable: string;\n}\n\nexport interface SearchResult {\n type: 'table' | 'column';\n schema: string;\n table: string;\n column?: string;\n columnType?: string;\n}\n\nexport interface TablesQuery {\n sql: string;\n normalize: (rows: Record<string, unknown>[]) => NormalizedTable[];\n}\n\nexport interface DescribeQuery {\n sql: string;\n normalize: (rows: Record<string, unknown>[]) => NormalizedColumn[];\n}\n\nexport interface SearchQuery {\n sql: string;\n normalize: (rows: Record<string, unknown>[]) => SearchResult[];\n}\n\n/**\n * Escape a SQL string literal value by doubling single quotes.\n */\nfunction escapeSqlValue(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\n/**\n * Normalize table_type values: 'BASE TABLE' -> 'TABLE', everything else uppercased.\n */\nfunction normalizeTableType(tableType: string): string {\n const upper = tableType.toUpperCase();\n if (upper === 'BASE TABLE') return 'TABLE';\n return upper;\n}\n\n/**\n * Helper to read a property from a row, trying lowercase first then uppercase.\n * MySQL drivers may return column names in either case.\n */\nfunction getField(row: Record<string, unknown>, lowercase: string, uppercase: string): unknown {\n if (lowercase in row) return row[lowercase];\n if (uppercase in row) return row[uppercase];\n return undefined;\n}\n\n/**\n * Generate a SQL query to list tables/views and a normalizer for the results.\n *\n * Supported types: duckdb, postgres, mysql, snowflake, sqlite.\n */\nexport function getTablesQuery(\n type: string,\n options?: { schema?: string; database?: string },\n): TablesQuery {\n switch (type) {\n case 'duckdb':\n case 'postgres': {\n const conditions = [\n \"table_schema NOT IN ('information_schema', 'pg_catalog')\",\n ];\n if (options?.schema) {\n conditions.push(`table_schema = '${escapeSqlValue(options.schema)}'`);\n }\n const sql = `SELECT table_schema, table_name, table_type FROM information_schema.tables WHERE ${conditions.join(' AND ')}`;\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n schema: String(row.table_schema),\n name: String(row.table_name),\n type: normalizeTableType(String(row.table_type)),\n })),\n };\n }\n\n case 'mysql': {\n let sql = 'SELECT table_schema, table_name, table_type FROM information_schema.tables';\n if (options?.schema) {\n sql += ` WHERE table_schema = '${escapeSqlValue(options.schema)}'`;\n }\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n schema: String(getField(row, 'table_schema', 'TABLE_SCHEMA')),\n name: String(getField(row, 'table_name', 'TABLE_NAME')),\n type: normalizeTableType(String(getField(row, 'table_type', 'TABLE_TYPE'))),\n })),\n };\n }\n\n case 'snowflake': {\n const conditions: string[] = [];\n if (options?.schema) {\n conditions.push(`TABLE_SCHEMA = '${escapeSqlValue(options.schema)}'`);\n }\n if (options?.database) {\n conditions.push(`TABLE_CATALOG = '${escapeSqlValue(options.database)}'`);\n }\n let sql = 'SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n schema: String(row.TABLE_SCHEMA),\n name: String(row.TABLE_NAME),\n type: normalizeTableType(String(row.TABLE_TYPE)),\n })),\n };\n }\n\n case 'sqlite': {\n const sql = \"SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'\";\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n schema: '',\n name: String(row.name),\n type: String(row.type).toUpperCase(),\n })),\n };\n }\n\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n}\n\n/**\n * Generate a SQL query to describe a table's columns and a normalizer for the results.\n *\n * Supported types: duckdb, postgres, mysql, snowflake, sqlite.\n * Accepts schema-qualified names (e.g. 'public.orders', 'ANALYTICS.PUBLIC.ORDERS').\n */\nexport function getDescribeQuery(type: string, table: string): DescribeQuery {\n switch (type) {\n case 'duckdb': {\n return {\n sql: `DESCRIBE ${table}`,\n normalize: (rows) =>\n rows.map((row) => ({\n name: String(row.column_name),\n type: String(row.column_type),\n nullable: String(row.null),\n })),\n };\n }\n\n case 'postgres': {\n const parts = table.split('.');\n const tableName = parts.length > 1 ? parts[parts.length - 1]! : table;\n const schemaName = parts.length > 1 ? parts[0]! : null;\n\n const conditions = [`table_name = '${escapeSqlValue(tableName)}'`];\n if (schemaName) {\n conditions.push(`table_schema = '${escapeSqlValue(schemaName)}'`);\n }\n\n const sql = `SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE ${conditions.join(' AND ')} ORDER BY ordinal_position`;\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n name: String(row.column_name),\n type: String(row.data_type),\n nullable: String(row.is_nullable),\n })),\n };\n }\n\n case 'mysql': {\n const parts = table.split('.');\n const tableName = parts.length > 1 ? parts[parts.length - 1]! : table;\n const schemaName = parts.length > 1 ? parts[0]! : null;\n\n const conditions = [`table_name = '${escapeSqlValue(tableName)}'`];\n if (schemaName) {\n conditions.push(`table_schema = '${escapeSqlValue(schemaName)}'`);\n }\n\n const sql = `SELECT column_name, column_type, is_nullable FROM information_schema.columns WHERE ${conditions.join(' AND ')} ORDER BY ordinal_position`;\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n name: String(getField(row, 'column_name', 'COLUMN_NAME')),\n type: String(getField(row, 'column_type', 'COLUMN_TYPE')),\n nullable: String(getField(row, 'is_nullable', 'IS_NULLABLE')),\n })),\n };\n }\n\n case 'snowflake': {\n return {\n sql: `DESCRIBE TABLE ${table}`,\n normalize: (rows) =>\n rows.map((row) => ({\n name: String(row.name),\n type: String(row.type),\n nullable: String(row['null?']) === 'Y' ? 'YES' : 'NO',\n })),\n };\n }\n\n case 'sqlite': {\n return {\n sql: `PRAGMA table_info(${table})`,\n normalize: (rows) =>\n rows.map((row) => ({\n name: String(row.name),\n type: String(row.type),\n nullable: Number(row.notnull) === 1 ? 'NO' : 'YES',\n })),\n };\n }\n\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n}\n\n/**\n * Generate a SQL query to search for tables and columns matching a keyword.\n *\n * Supported types: duckdb, postgres, mysql, snowflake, sqlite.\n */\nexport function getSearchQuery(type: string, keyword: string): SearchQuery {\n const escaped = escapeSqlValue(keyword);\n\n switch (type) {\n case 'duckdb':\n case 'postgres': {\n const sql = [\n `SELECT 'table' AS match_type, table_schema, table_name, NULL AS column_name, NULL AS data_type`,\n `FROM information_schema.tables`,\n `WHERE table_schema NOT IN ('information_schema', 'pg_catalog')`,\n `AND table_name ILIKE '%${escaped}%'`,\n `UNION ALL`,\n `SELECT 'column' AS match_type, table_schema, table_name, column_name, data_type`,\n `FROM information_schema.columns`,\n `WHERE table_schema NOT IN ('information_schema', 'pg_catalog')`,\n `AND column_name ILIKE '%${escaped}%'`,\n `ORDER BY match_type, table_schema, table_name`,\n ].join(' ');\n\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n type: String(row.match_type) as 'table' | 'column',\n schema: String(row.table_schema),\n table: String(row.table_name),\n ...(row.column_name ? { column: String(row.column_name) } : {}),\n ...(row.data_type ? { columnType: String(row.data_type) } : {}),\n })),\n };\n }\n\n case 'mysql': {\n const sql = [\n `SELECT 'table' AS match_type, table_schema, table_name, NULL AS column_name, NULL AS data_type`,\n `FROM information_schema.tables`,\n `WHERE table_name LIKE '%${escaped}%'`,\n `UNION ALL`,\n `SELECT 'column' AS match_type, table_schema, table_name, column_name, data_type`,\n `FROM information_schema.columns`,\n `WHERE column_name LIKE '%${escaped}%'`,\n `ORDER BY match_type, table_schema, table_name`,\n ].join(' ');\n\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n type: String(getField(row, 'match_type', 'MATCH_TYPE')) as 'table' | 'column',\n schema: String(getField(row, 'table_schema', 'TABLE_SCHEMA')),\n table: String(getField(row, 'table_name', 'TABLE_NAME')),\n ...(getField(row, 'column_name', 'COLUMN_NAME')\n ? { column: String(getField(row, 'column_name', 'COLUMN_NAME')) }\n : {}),\n ...(getField(row, 'data_type', 'DATA_TYPE')\n ? { columnType: String(getField(row, 'data_type', 'DATA_TYPE')) }\n : {}),\n })),\n };\n }\n\n case 'snowflake': {\n const sql = [\n `SELECT 'table' AS MATCH_TYPE, TABLE_SCHEMA, TABLE_NAME, NULL AS COLUMN_NAME, NULL AS DATA_TYPE`,\n `FROM INFORMATION_SCHEMA.TABLES`,\n `WHERE TABLE_NAME ILIKE '%${escaped}%'`,\n `UNION ALL`,\n `SELECT 'column' AS MATCH_TYPE, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE`,\n `FROM INFORMATION_SCHEMA.COLUMNS`,\n `WHERE COLUMN_NAME ILIKE '%${escaped}%'`,\n `ORDER BY MATCH_TYPE, TABLE_SCHEMA, TABLE_NAME`,\n ].join(' ');\n\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n type: String(row.MATCH_TYPE) as 'table' | 'column',\n schema: String(row.TABLE_SCHEMA),\n table: String(row.TABLE_NAME),\n ...(row.COLUMN_NAME ? { column: String(row.COLUMN_NAME) } : {}),\n ...(row.DATA_TYPE ? { columnType: String(row.DATA_TYPE) } : {}),\n })),\n };\n }\n\n case 'sqlite': {\n const sql = `SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' AND name LIKE '%${escaped}%'`;\n\n return {\n sql,\n normalize: (rows) =>\n rows.map((row) => ({\n type: 'table' as const,\n schema: '',\n table: String(row.name),\n })),\n };\n }\n\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n}\n"],"mappings":"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","names":[]}
|