yamchart 0.9.4 → 0.10.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-SC64RTZO.js → advisor-O2BRAI4T.js} +24 -14
- package/dist/advisor-O2BRAI4T.js.map +1 -0
- package/dist/{chunk-NXQ6ZO3V.js → chunk-5FHV22X2.js} +6 -6
- package/dist/chunk-5FHV22X2.js.map +1 -0
- package/dist/{chunk-7CD6UQAV.js → chunk-DZVT6PHW.js} +9 -1
- package/dist/chunk-DZVT6PHW.js.map +1 -0
- package/dist/chunk-FN6R2LAC.js +15442 -0
- package/dist/chunk-FN6R2LAC.js.map +1 -0
- package/dist/chunk-QJ5CPQJK.js +2053 -0
- package/dist/chunk-QJ5CPQJK.js.map +1 -0
- package/dist/{chunk-S7YQXEKM.js → chunk-QUIDZO5G.js} +103 -275
- package/dist/chunk-QUIDZO5G.js.map +1 -0
- package/dist/{chunk-RMIDEBHD.js → chunk-S2CH4HUZ.js} +6 -6
- package/dist/{chunk-UND73EOB.js → chunk-UFDQ3C7Q.js} +473 -4
- package/dist/chunk-UFDQ3C7Q.js.map +1 -0
- package/dist/{chunk-H4L3FNLS.js → chunk-ZBCQNWVN.js} +2 -2
- package/dist/{chunk-RM6MNDVF.js → chunk-ZIY22VO7.js} +192 -11
- package/dist/chunk-ZIY22VO7.js.map +1 -0
- package/dist/{connection-utils-C4FQGBW6.js → connection-utils-FEUWER5I.js} +5 -5
- package/dist/{describe-X75C2VDU.js → describe-MEP72B56.js} +6 -6
- package/dist/{dev-7YLRQ6SA.js → dev-LZ4YHNDJ.js} +1334 -77
- package/dist/dev-LZ4YHNDJ.js.map +1 -0
- package/dist/dist-5IFWLWND.js +48 -0
- package/dist/{dist-NGQG7Z4G.js → dist-MIKFZKSD.js} +2 -2
- package/dist/{dist-MX5K2ABB.js → dist-PPAD6KOM.js} +44 -2
- package/dist/{dist-JMLAZUL7.js → dist-XNCED7JW.js} +29 -12
- package/dist/fileFromPath-7TNUU6RI.js +130 -0
- package/dist/fileFromPath-7TNUU6RI.js.map +1 -0
- package/dist/index.js +25 -25
- package/dist/public/assets/DataView-DUCz_96y.js +9 -0
- package/dist/public/assets/{EventManagement-B7leMxfo.js → EventManagement-BnmeJDQl.js} +1 -1
- package/dist/public/assets/{ExplorePage-zI1OiYlH.js → ExplorePage-kk4z9ldZ.js} +1 -1
- package/dist/public/assets/{LoginPage-Dyk2ILmo.js → LoginPage-CzaFkkjg.js} +1 -1
- package/dist/public/assets/{PublicViewer-C2tNYBR2.js → PublicViewer-irjxqH6a.js} +1 -1
- package/dist/public/assets/{SetupWizard-BzZSJlbt.js → SetupWizard-ConWIcmy.js} +1 -1
- package/dist/public/assets/{ShareManagement-C7RIZRWe.js → ShareManagement-CP4wdwLR.js} +1 -1
- package/dist/public/assets/SourceDetailView-DZS5518E.js +1 -0
- package/dist/public/assets/{UserManagement-BD-lLbVH.js → UserManagement-AubGd9hl.js} +1 -1
- package/dist/public/assets/data-3vtzSuAZ.js +1 -0
- package/dist/public/assets/{index-CfyF2Wf-.css → index-C1X8RW4Z.css} +1 -1
- package/dist/public/assets/{index-lklRbe2I.js → index-jlfTO7f5.js} +44 -44
- package/dist/public/assets/{index.es-CLyC5-GY.js → index.es-CgnvEWi5.js} +1 -1
- package/dist/public/assets/{jspdf.es.min-CTZVk96E.js → jspdf.es.min-Cw5WefMt.js} +3 -3
- package/dist/public/index.html +2 -2
- package/dist/{query-QNRDS74I.js → query-2H3YOPI2.js} +5 -5
- package/dist/{reset-password-HDCLH7PZ.js → reset-password-YVCZKZPC.js} +2 -2
- package/dist/{sample-SKLHBZBU.js → sample-ODUGGSFA.js} +5 -5
- package/dist/{search-4KMETZVX.js → search-IPE4ISFB.js} +6 -6
- package/dist/{semantic-6WKELH5V.js → semantic-K3MYXXJI.js} +3 -2
- package/dist/{semantic-6WKELH5V.js.map → semantic-K3MYXXJI.js.map} +1 -1
- package/dist/{source-resolver-R7WBIL7M.js → source-resolver-HZQLOODU.js} +6 -6
- package/dist/source-resolver-HZQLOODU.js.map +1 -0
- package/dist/{sync-warehouse-UWRNUXE7.js → sync-warehouse-26L6JDSV.js} +10 -10
- package/dist/{tables-V65QUGHK.js → tables-K5NAN2WK.js} +6 -6
- package/dist/templates/default/docs/yamchart-reference.md +46 -0
- package/dist/{test-UE5OWG3E.js → test-SRHVOXZB.js} +8 -7
- package/dist/{test-UE5OWG3E.js.map → test-SRHVOXZB.js.map} +1 -1
- package/package.json +2 -2
- package/dist/advisor-SC64RTZO.js.map +0 -1
- package/dist/chunk-7CD6UQAV.js.map +0 -1
- package/dist/chunk-NXQ6ZO3V.js.map +0 -1
- package/dist/chunk-RM6MNDVF.js.map +0 -1
- package/dist/chunk-S7YQXEKM.js.map +0 -1
- package/dist/chunk-UND73EOB.js.map +0 -1
- package/dist/dev-7YLRQ6SA.js.map +0 -1
- package/dist/dist-MNXSMGV6.js +0 -790
- package/dist/dist-MNXSMGV6.js.map +0 -1
- /package/dist/{chunk-RMIDEBHD.js.map → chunk-S2CH4HUZ.js.map} +0 -0
- /package/dist/{chunk-H4L3FNLS.js.map → chunk-ZBCQNWVN.js.map} +0 -0
- /package/dist/{connection-utils-C4FQGBW6.js.map → connection-utils-FEUWER5I.js.map} +0 -0
- /package/dist/{describe-X75C2VDU.js.map → describe-MEP72B56.js.map} +0 -0
- /package/dist/{dist-JMLAZUL7.js.map → dist-5IFWLWND.js.map} +0 -0
- /package/dist/{dist-MX5K2ABB.js.map → dist-MIKFZKSD.js.map} +0 -0
- /package/dist/{dist-NGQG7Z4G.js.map → dist-PPAD6KOM.js.map} +0 -0
- /package/dist/{source-resolver-R7WBIL7M.js.map → dist-XNCED7JW.js.map} +0 -0
- /package/dist/{query-QNRDS74I.js.map → query-2H3YOPI2.js.map} +0 -0
- /package/dist/{reset-password-HDCLH7PZ.js.map → reset-password-YVCZKZPC.js.map} +0 -0
- /package/dist/{sample-SKLHBZBU.js.map → sample-ODUGGSFA.js.map} +0 -0
- /package/dist/{search-4KMETZVX.js.map → search-IPE4ISFB.js.map} +0 -0
- /package/dist/{sync-warehouse-UWRNUXE7.js.map → sync-warehouse-26L6JDSV.js.map} +0 -0
- /package/dist/{tables-V65QUGHK.js.map → tables-K5NAN2WK.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import { resolve } from "path";
|
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { randomBytes } from "crypto";
|
|
14
14
|
async function resetPassword(projectDir, email) {
|
|
15
|
-
const { AuthDatabase, hashPassword } = await import("./dist-
|
|
15
|
+
const { AuthDatabase, hashPassword } = await import("./dist-MIKFZKSD.js");
|
|
16
16
|
const { readFileSync } = await import("fs");
|
|
17
17
|
const { parse } = await import("yaml");
|
|
18
18
|
const projectPath = resolve(projectDir, "yamchart.yaml");
|
|
@@ -57,4 +57,4 @@ async function resetPassword(projectDir, email) {
|
|
|
57
57
|
export {
|
|
58
58
|
resetPassword
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=reset-password-
|
|
60
|
+
//# sourceMappingURL=reset-password-YVCZKZPC.js.map
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createConnector,
|
|
6
6
|
resolveConnection
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-5FHV22X2.js";
|
|
8
|
+
import "./chunk-QUIDZO5G.js";
|
|
9
|
+
import "./chunk-UFDQ3C7Q.js";
|
|
10
|
+
import "./chunk-ZIY22VO7.js";
|
|
11
11
|
import "./chunk-7D4SUZUM.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/sample.ts
|
|
@@ -38,4 +38,4 @@ async function sampleTable(projectDir, table, options) {
|
|
|
38
38
|
export {
|
|
39
39
|
sampleTable
|
|
40
40
|
};
|
|
41
|
-
//# sourceMappingURL=sample-
|
|
41
|
+
//# sourceMappingURL=sample-ODUGGSFA.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveSearchSource
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZBCQNWVN.js";
|
|
4
4
|
import "./chunk-VJC24RKT.js";
|
|
5
5
|
import "./chunk-EAQXUGP6.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-5FHV22X2.js";
|
|
7
|
+
import "./chunk-QUIDZO5G.js";
|
|
8
|
+
import "./chunk-UFDQ3C7Q.js";
|
|
9
|
+
import "./chunk-ZIY22VO7.js";
|
|
10
10
|
import "./chunk-7D4SUZUM.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/search.ts
|
|
@@ -25,4 +25,4 @@ async function searchDatabase(projectDir, keyword, options) {
|
|
|
25
25
|
export {
|
|
26
26
|
searchDatabase
|
|
27
27
|
};
|
|
28
|
-
//# sourceMappingURL=search-
|
|
28
|
+
//# sourceMappingURL=search-IPE4ISFB.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SemanticModelBuilder,
|
|
3
3
|
SemanticQueryCompiler
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UFDQ3C7Q.js";
|
|
5
|
+
import "./chunk-ZIY22VO7.js";
|
|
5
6
|
import "./chunk-7D4SUZUM.js";
|
|
6
7
|
|
|
7
8
|
// src/commands/semantic.ts
|
|
@@ -36,4 +37,4 @@ export {
|
|
|
36
37
|
listSemanticEntities,
|
|
37
38
|
querySemanticEntity
|
|
38
39
|
};
|
|
39
|
-
//# sourceMappingURL=semantic-
|
|
40
|
+
//# sourceMappingURL=semantic-K3MYXXJI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/semantic.ts"],"sourcesContent":["import { SemanticModelBuilder, SemanticQueryCompiler } from '@yamchart/query/semantic';\nimport type { SemanticFilter } from '@yamchart/schema';\n\ninterface CatalogJsonData {\n models: Array<{\n name: string;\n table?: string;\n description?: string;\n source?: string;\n path?: string;\n columns?: Array<{ name: string; data_type?: string; description?: string; hints?: string[] }>;\n }>;\n}\n\nexport interface SemanticEntitySummary {\n name: string;\n source_table: string;\n description?: string;\n measures: Array<{ name: string; type: string }>;\n dimensions: Array<{ name: string; type: string }>;\n}\n\nexport interface ListResult {\n entities: SemanticEntitySummary[];\n}\n\nexport interface QueryInput {\n entity: string;\n measures: string[];\n dimensions: string[];\n filters: Array<{ dimension: string; operator: string; value: string | number | boolean | Array<string | number> }>;\n order_by?: { field: string; direction: string };\n limit: number;\n}\n\nexport interface QueryResult {\n sql: string;\n entityName: string;\n}\n\nexport function listSemanticEntities(catalog: CatalogJsonData): ListResult {\n const builder = new SemanticModelBuilder();\n const model = builder.build({ catalog: { models: catalog.models } });\n\n return {\n entities: model.entities.map((e) => ({\n name: e.name,\n source_table: e.source_table,\n description: e.description,\n measures: e.measures.map((m) => ({ name: m.name, type: m.type })),\n dimensions: e.dimensions.map((d) => ({ name: d.name, type: d.type })),\n })),\n };\n}\n\nexport function querySemanticEntity(catalog: CatalogJsonData, input: QueryInput): QueryResult {\n const builder = new SemanticModelBuilder();\n const model = builder.build({ catalog: { models: catalog.models } });\n\n const compiler = new SemanticQueryCompiler();\n const sql = compiler.compile(model, {\n model: input.entity,\n measures: input.measures,\n dimensions: input.dimensions,\n filters: input.filters as SemanticFilter[],\n order_by: input.order_by ? { field: input.order_by.field, direction: input.order_by.direction as 'asc' | 'desc' } : undefined,\n limit: input.limit,\n });\n\n return { sql, entityName: input.entity };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/semantic.ts"],"sourcesContent":["import { SemanticModelBuilder, SemanticQueryCompiler } from '@yamchart/query/semantic';\nimport type { SemanticFilter } from '@yamchart/schema';\n\ninterface CatalogJsonData {\n models: Array<{\n name: string;\n table?: string;\n description?: string;\n source?: string;\n path?: string;\n columns?: Array<{ name: string; data_type?: string; description?: string; hints?: string[] }>;\n }>;\n}\n\nexport interface SemanticEntitySummary {\n name: string;\n source_table: string;\n description?: string;\n measures: Array<{ name: string; type: string }>;\n dimensions: Array<{ name: string; type: string }>;\n}\n\nexport interface ListResult {\n entities: SemanticEntitySummary[];\n}\n\nexport interface QueryInput {\n entity: string;\n measures: string[];\n dimensions: string[];\n filters: Array<{ dimension: string; operator: string; value: string | number | boolean | Array<string | number> }>;\n order_by?: { field: string; direction: string };\n limit: number;\n}\n\nexport interface QueryResult {\n sql: string;\n entityName: string;\n}\n\nexport function listSemanticEntities(catalog: CatalogJsonData): ListResult {\n const builder = new SemanticModelBuilder();\n const model = builder.build({ catalog: { models: catalog.models } });\n\n return {\n entities: model.entities.map((e) => ({\n name: e.name,\n source_table: e.source_table,\n description: e.description,\n measures: e.measures.map((m) => ({ name: m.name, type: m.type })),\n dimensions: e.dimensions.map((d) => ({ name: d.name, type: d.type })),\n })),\n };\n}\n\nexport function querySemanticEntity(catalog: CatalogJsonData, input: QueryInput): QueryResult {\n const builder = new SemanticModelBuilder();\n const model = builder.build({ catalog: { models: catalog.models } });\n\n const compiler = new SemanticQueryCompiler();\n const sql = compiler.compile(model, {\n model: input.entity,\n measures: input.measures,\n dimensions: input.dimensions,\n filters: input.filters as SemanticFilter[],\n order_by: input.order_by ? { field: input.order_by.field, direction: input.order_by.direction as 'asc' | 'desc' } : undefined,\n limit: input.limit,\n });\n\n return { sql, entityName: input.entity };\n}\n"],"mappings":";;;;;;;;AAwCO,SAAS,qBAAqB,SAAsC;AACzE,QAAM,UAAU,IAAI,qBAAqB;AACzC,QAAM,QAAQ,QAAQ,MAAM,EAAE,SAAS,EAAE,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAEnE,SAAO;AAAA,IACL,UAAU,MAAM,SAAS,IAAI,CAAC,OAAO;AAAA,MACnC,MAAM,EAAE;AAAA,MACR,cAAc,EAAE;AAAA,MAChB,aAAa,EAAE;AAAA,MACf,UAAU,EAAE,SAAS,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAAA,MAChE,YAAY,EAAE,WAAW,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AAAA,IACtE,EAAE;AAAA,EACJ;AACF;AAEO,SAAS,oBAAoB,SAA0B,OAAgC;AAC5F,QAAM,UAAU,IAAI,qBAAqB;AACzC,QAAM,QAAQ,QAAQ,MAAM,EAAE,SAAS,EAAE,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAEnE,QAAM,WAAW,IAAI,sBAAsB;AAC3C,QAAM,MAAM,SAAS,QAAQ,OAAO;AAAA,IAClC,OAAO,MAAM;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM;AAAA,IACf,UAAU,MAAM,WAAW,EAAE,OAAO,MAAM,SAAS,OAAO,WAAW,MAAM,SAAS,UAA4B,IAAI;AAAA,IACpH,OAAO,MAAM;AAAA,EACf,CAAC;AAED,SAAO,EAAE,KAAK,YAAY,MAAM,OAAO;AACzC;","names":[]}
|
|
@@ -2,17 +2,17 @@ import {
|
|
|
2
2
|
resolveDescribeSource,
|
|
3
3
|
resolveSearchSource,
|
|
4
4
|
resolveTablesSource
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZBCQNWVN.js";
|
|
6
6
|
import "./chunk-VJC24RKT.js";
|
|
7
7
|
import "./chunk-EAQXUGP6.js";
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-5FHV22X2.js";
|
|
9
|
+
import "./chunk-QUIDZO5G.js";
|
|
10
|
+
import "./chunk-UFDQ3C7Q.js";
|
|
11
|
+
import "./chunk-ZIY22VO7.js";
|
|
12
12
|
import "./chunk-7D4SUZUM.js";
|
|
13
13
|
export {
|
|
14
14
|
resolveDescribeSource,
|
|
15
15
|
resolveSearchSource,
|
|
16
16
|
resolveTablesSource
|
|
17
17
|
};
|
|
18
|
-
//# sourceMappingURL=source-resolver-
|
|
18
|
+
//# sourceMappingURL=source-resolver-HZQLOODU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
generateCatalogJson,
|
|
3
|
-
generateCatalogMd,
|
|
4
|
-
generateWarehouseSection
|
|
5
|
-
} from "./chunk-WYS4ULBE.js";
|
|
6
1
|
import {
|
|
7
2
|
getColumnsQuery,
|
|
8
3
|
getDescribeQuery,
|
|
9
4
|
getTablesQuery
|
|
10
5
|
} from "./chunk-EAQXUGP6.js";
|
|
6
|
+
import {
|
|
7
|
+
generateCatalogJson,
|
|
8
|
+
generateCatalogMd,
|
|
9
|
+
generateWarehouseSection
|
|
10
|
+
} from "./chunk-WYS4ULBE.js";
|
|
11
11
|
import {
|
|
12
12
|
createConnector,
|
|
13
13
|
resolveConnection
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-5FHV22X2.js";
|
|
15
15
|
import {
|
|
16
16
|
detail,
|
|
17
17
|
error,
|
|
@@ -20,9 +20,9 @@ import {
|
|
|
20
20
|
success,
|
|
21
21
|
warning
|
|
22
22
|
} from "./chunk-HJVVHYVN.js";
|
|
23
|
-
import "./chunk-
|
|
24
|
-
import "./chunk-
|
|
25
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-QUIDZO5G.js";
|
|
24
|
+
import "./chunk-UFDQ3C7Q.js";
|
|
25
|
+
import "./chunk-ZIY22VO7.js";
|
|
26
26
|
import "./chunk-7D4SUZUM.js";
|
|
27
27
|
|
|
28
28
|
// src/commands/warehouse-sync.ts
|
|
@@ -391,4 +391,4 @@ async function runSyncWarehouse(projectDir, options) {
|
|
|
391
391
|
export {
|
|
392
392
|
runSyncWarehouse
|
|
393
393
|
};
|
|
394
|
-
//# sourceMappingURL=sync-warehouse-
|
|
394
|
+
//# sourceMappingURL=sync-warehouse-26L6JDSV.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveTablesSource
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZBCQNWVN.js";
|
|
4
4
|
import "./chunk-VJC24RKT.js";
|
|
5
5
|
import "./chunk-EAQXUGP6.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-5FHV22X2.js";
|
|
7
|
+
import "./chunk-QUIDZO5G.js";
|
|
8
|
+
import "./chunk-UFDQ3C7Q.js";
|
|
9
|
+
import "./chunk-ZIY22VO7.js";
|
|
10
10
|
import "./chunk-7D4SUZUM.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/tables.ts
|
|
@@ -27,4 +27,4 @@ async function listTables(projectDir, options) {
|
|
|
27
27
|
export {
|
|
28
28
|
listTables
|
|
29
29
|
};
|
|
30
|
-
//# sourceMappingURL=tables-
|
|
30
|
+
//# sourceMappingURL=tables-K5NAN2WK.js.map
|
|
@@ -819,6 +819,10 @@ auth:
|
|
|
819
819
|
SSO — anyone else is rejected, not auto-provisioned. Without it, any authenticated
|
|
820
820
|
account is auto-provisioned as a viewer. `admin_emails` are provisioned as admin
|
|
821
821
|
and are always allowed.
|
|
822
|
+
- **Admin SSO invites:** in the admin user manager, "Add User" with a **blank
|
|
823
|
+
password** creates an SSO invite (email + role, no password). When that person
|
|
824
|
+
signs in via SSO, their identity is linked to the invite and they get the assigned
|
|
825
|
+
role — no config edit or redeploy needed.
|
|
822
826
|
- **Roles:** admin (manage users + content), editor (content only), viewer (read-only)
|
|
823
827
|
- **User attributes:** Admins can set key-value pairs per user (e.g. `department: Sales`) for row-level security
|
|
824
828
|
- **RLS in SQL:** `{{ user.email }}`, `{{ user.role }}`, `{{ user.department }}` etc.
|
|
@@ -1297,3 +1301,45 @@ and operates as your yamchart user — your role and row-level security apply
|
|
|
1297
1301
|
(remember: `run_sql` runs raw SQL without RLS; only grant MCP access to trusted
|
|
1298
1302
|
users). Set `BASE_URL` (or `defaults.base_url`) so the OAuth metadata advertises
|
|
1299
1303
|
correct absolute URLs.
|
|
1304
|
+
|
|
1305
|
+
---
|
|
1306
|
+
|
|
1307
|
+
## New in v0.10 — Semantic layer, active learning & grounded answering
|
|
1308
|
+
|
|
1309
|
+
### Semantic layer (governed metrics)
|
|
1310
|
+
Define metrics once in `semantic/*.yaml` (measure + aggregation + source table + governed named
|
|
1311
|
+
filters + synonyms). Yamchart compiles `{metrics, dimensions, grain, time_range, filters}` → SQL,
|
|
1312
|
+
so business logic is governed in Git instead of re-guessed per query. MCP tools `list_metrics`,
|
|
1313
|
+
`get_metric`, `query_metric` (returns data + the generated SQL + a ready-to-use chart spec).
|
|
1314
|
+
|
|
1315
|
+
### MCP chart-authoring DX
|
|
1316
|
+
`get_chart_schema`, `validate_chart`, `get_chart_spec`; field-level chart validation errors;
|
|
1317
|
+
`describe_table` accepts `{schema, table}` or `{table}`.
|
|
1318
|
+
|
|
1319
|
+
### Hunches — active-learning onboarding
|
|
1320
|
+
`POST /api/learn` profiles a source (column stats, null rates, top values) and an LLM proposes
|
|
1321
|
+
question→SQL **Hunches**; review them (confirm / edit / run-sample / reject) in the **Data** section.
|
|
1322
|
+
Confirmed Hunches accumulate as governed knowledge.
|
|
1323
|
+
|
|
1324
|
+
### Blindspots + knowledge context
|
|
1325
|
+
A Blindspots scan surfaces `context` items (ambiguities it needs you to explain) and `opportunity`
|
|
1326
|
+
items (suggested new marts). Author business context as `knowledge/*.md` or per-source docs in the
|
|
1327
|
+
UI; both feed Hunch + Blindspot generation.
|
|
1328
|
+
|
|
1329
|
+
### Data Sources hub
|
|
1330
|
+
A single **Data** workspace: sources → tables (catalog + profile stats) + knowledge; plus the
|
|
1331
|
+
Hunches and Blindspots tabs. The account/profile menu is always visible; auth-coupled items appear
|
|
1332
|
+
only when auth is enabled.
|
|
1333
|
+
|
|
1334
|
+
### Grounded answering (MCP `answer_question`)
|
|
1335
|
+
`answer_question(question)` retrieves the source's confirmed knowledge by embedding similarity
|
|
1336
|
+
(in-app cosine; no pgvector) and either reuses a matching Hunch's SQL or generates SQL grounded in
|
|
1337
|
+
the closest examples — read-only validated. Returns `{ sql, mode, groundedOn, rows }`.
|
|
1338
|
+
|
|
1339
|
+
### Multi-provider LLM + knowledge store
|
|
1340
|
+
- **Providers:** `ai:` block with named providers + per-task routing — Anthropic, OpenAI, Gemini,
|
|
1341
|
+
Ollama (local) and Ollama Cloud, via one OpenAI-compatible provider. Tasks: `hunch_generation`,
|
|
1342
|
+
`blindspot_scan`, `advisor`, `answer`. `ai.embedding` configures the embedding provider/model.
|
|
1343
|
+
- **Knowledge store:** Hunches/Blindspots/knowledge/embeddings persist in Postgres via
|
|
1344
|
+
`KNOWLEDGE_DATABASE_URL` (Supabase recommended). Authored config stays in Git; learned knowledge
|
|
1345
|
+
lives in the DB. Core yamchart (charts/dashboards/models) still needs no database.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createConnector,
|
|
3
3
|
resolveConnection
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5FHV22X2.js";
|
|
5
5
|
import {
|
|
6
6
|
detail,
|
|
7
7
|
error,
|
|
@@ -10,17 +10,18 @@ import {
|
|
|
10
10
|
success,
|
|
11
11
|
warning
|
|
12
12
|
} from "./chunk-HJVVHYVN.js";
|
|
13
|
-
import "./chunk-RM6MNDVF.js";
|
|
14
13
|
import {
|
|
15
14
|
createTemplateContext,
|
|
16
|
-
expandDatePreset,
|
|
17
|
-
isDatePreset,
|
|
18
15
|
parseModelMetadata,
|
|
19
16
|
renderTemplate,
|
|
20
17
|
resolveDynamicDefault,
|
|
21
18
|
runAll
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import
|
|
19
|
+
} from "./chunk-QUIDZO5G.js";
|
|
20
|
+
import {
|
|
21
|
+
expandDatePreset,
|
|
22
|
+
isDatePreset
|
|
23
|
+
} from "./chunk-UFDQ3C7Q.js";
|
|
24
|
+
import "./chunk-ZIY22VO7.js";
|
|
24
25
|
import "./chunk-7D4SUZUM.js";
|
|
25
26
|
|
|
26
27
|
// src/commands/test.ts
|
|
@@ -180,4 +181,4 @@ export {
|
|
|
180
181
|
formatTestOutput,
|
|
181
182
|
testProject
|
|
182
183
|
};
|
|
183
|
-
//# sourceMappingURL=test-
|
|
184
|
+
//# sourceMappingURL=test-SRHVOXZB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir } from 'fs/promises';\nimport { join, extname } from 'path';\nimport {\n parseModelMetadata,\n runAll,\n renderTemplate,\n createTemplateContext,\n expandDatePreset,\n isDatePreset,\n resolveDynamicDefault,\n type TestSuiteResult,\n type TestModelInput,\n} from '@yamchart/query';\nimport type { ModelMetadata } from '@yamchart/schema';\nimport * as output from '../utils/output.js';\nimport { resolveConnection, createConnector } from './connection-utils.js';\n\nexport interface TestOptions {\n connection?: string;\n json?: boolean;\n}\n\nexport interface TestResult {\n success: boolean;\n suite: TestSuiteResult;\n connectionName: string;\n}\n\nexport async function testProject(\n projectDir: string,\n modelFilter: string | undefined,\n options: TestOptions,\n): Promise<TestResult> {\n const connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\n\n const modelsDir = join(projectDir, 'models');\n const allModels = await loadModels(modelsDir);\n\n let modelsToTest = allModels;\n if (modelFilter) {\n modelsToTest = allModels.filter((m) => m.name === modelFilter);\n if (modelsToTest.length === 0) {\n throw new Error(`Model \"${modelFilter}\" not found`);\n }\n }\n\n // Build refs map: model name -> model name (identity for standalone execution)\n const refs: Record<string, string> = {};\n for (const m of allModels) {\n refs[m.name] = m.name;\n }\n\n const testInputs: TestModelInput[] = [];\n for (const model of modelsToTest) {\n try {\n const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);\n testInputs.push({ compiledSql, metadata: model.metadata });\n } catch {\n // If compilation fails, include raw SQL so the error is reported during test execution\n testInputs.push({ compiledSql: model.sql, metadata: model.metadata });\n }\n }\n\n try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connection.name };\n } finally {\n await connector.disconnect();\n }\n}\n\nfunction compileWithDefaults(\n sql: string,\n metadata: ModelMetadata,\n refs: Record<string, string>,\n): string {\n const params: Record<string, unknown> = {};\n if (metadata.params) {\n for (const p of metadata.params) {\n if (p.default !== undefined) {\n params[p.name] = resolveDynamicDefault(p.default);\n }\n }\n }\n\n // Expand date presets into start_date/end_date\n if (typeof params.date_range === 'string' && isDatePreset(params.date_range)) {\n const range = expandDatePreset(params.date_range);\n if (range) {\n params.start_date = range.start_date;\n params.end_date = range.end_date;\n }\n }\n\n const context = createTemplateContext(params, refs);\n return renderTemplate(sql, context);\n}\n\ninterface LoadedModel {\n name: string;\n sql: string;\n metadata: ModelMetadata;\n}\n\nasync function loadModels(dir: string): Promise<LoadedModel[]> {\n const models: LoadedModel[] = [];\n\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return models;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n const subModels = await loadModels(fullPath);\n models.push(...subModels);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n try {\n const parsed = parseModelMetadata(content);\n models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });\n } catch {\n // Skip unparseable models\n }\n }\n }\n\n return models;\n}\n\nexport function formatTestOutput(result: TestResult, connectionName: string): void {\n const { suite } = result;\n\n const testedModels = suite.models.filter(\n (m) => m.schemaCheck || m.assertions.length > 0 || m.error,\n );\n\n output.header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);\n\n for (const model of suite.models) {\n const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;\n if (!hasChecks) continue;\n\n console.log(` ${model.modelName}`);\n\n if (model.error) {\n output.error(` error: ${model.error}`);\n continue;\n }\n\n if (model.schemaCheck) {\n formatSchemaCheck(model.schemaCheck);\n }\n\n for (const assertion of model.assertions) {\n formatAssertion(assertion);\n }\n\n output.newline();\n }\n\n // Summary line\n const totalChecks = suite.passed + suite.failed;\n const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;\n const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : '';\n const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;\n\n if (suite.failed === 0) {\n output.success(`${summary}${skippedSuffix}${durationSuffix}`);\n } else {\n output.error(`${summary}${skippedSuffix}${durationSuffix}`);\n }\n}\n\nfunction formatSchemaCheck(check: NonNullable<TestSuiteResult['models'][0]['schemaCheck']>): void {\n if (check.passed) {\n output.success(` schema: returns expected columns (${check.expectedColumns.join(', ')})`);\n return;\n }\n\n const issues: string[] = [];\n if (check.missingColumns.length > 0) {\n issues.push(`missing: ${check.missingColumns.join(', ')}`);\n }\n for (const m of check.typeMismatches) {\n issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);\n }\n output.error(` schema: ${issues.join('; ')}`);\n}\n\nfunction formatAssertion(assertion: TestSuiteResult['models'][0]['assertions'][0]): void {\n const label = extractAssertionLabel(assertion.sql);\n\n if (assertion.warning) {\n output.warning(` ${label}`);\n output.detail(` ${assertion.warning}`);\n }\n\n if (assertion.error) {\n output.error(` test: ${label} -- error`);\n output.detail(` ${assertion.error}`);\n } else if (assertion.passed) {\n output.success(` test: ${label}`);\n } else {\n output.error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);\n if (assertion.sampleViolations) {\n for (const row of assertion.sampleViolations.slice(0, 3)) {\n output.detail(` ${JSON.stringify(row)}`);\n }\n }\n }\n}\n\nfunction extractAssertionLabel(sql: string): string {\n const whereMatch = sql.match(/WHERE\\s+(.+?)$/i);\n if (whereMatch?.[1]) {\n const clause = whereMatch[1].trim();\n return clause.length > 60 ? clause.slice(0, 57) + '...' : clause;\n }\n return sql.length > 60 ? sql.slice(0, 57) + '...' : sql;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir } from 'fs/promises';\nimport { join, extname } from 'path';\nimport {\n parseModelMetadata,\n runAll,\n renderTemplate,\n createTemplateContext,\n expandDatePreset,\n isDatePreset,\n resolveDynamicDefault,\n type TestSuiteResult,\n type TestModelInput,\n} from '@yamchart/query';\nimport type { ModelMetadata } from '@yamchart/schema';\nimport * as output from '../utils/output.js';\nimport { resolveConnection, createConnector } from './connection-utils.js';\n\nexport interface TestOptions {\n connection?: string;\n json?: boolean;\n}\n\nexport interface TestResult {\n success: boolean;\n suite: TestSuiteResult;\n connectionName: string;\n}\n\nexport async function testProject(\n projectDir: string,\n modelFilter: string | undefined,\n options: TestOptions,\n): Promise<TestResult> {\n const connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\n\n const modelsDir = join(projectDir, 'models');\n const allModels = await loadModels(modelsDir);\n\n let modelsToTest = allModels;\n if (modelFilter) {\n modelsToTest = allModels.filter((m) => m.name === modelFilter);\n if (modelsToTest.length === 0) {\n throw new Error(`Model \"${modelFilter}\" not found`);\n }\n }\n\n // Build refs map: model name -> model name (identity for standalone execution)\n const refs: Record<string, string> = {};\n for (const m of allModels) {\n refs[m.name] = m.name;\n }\n\n const testInputs: TestModelInput[] = [];\n for (const model of modelsToTest) {\n try {\n const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);\n testInputs.push({ compiledSql, metadata: model.metadata });\n } catch {\n // If compilation fails, include raw SQL so the error is reported during test execution\n testInputs.push({ compiledSql: model.sql, metadata: model.metadata });\n }\n }\n\n try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connection.name };\n } finally {\n await connector.disconnect();\n }\n}\n\nfunction compileWithDefaults(\n sql: string,\n metadata: ModelMetadata,\n refs: Record<string, string>,\n): string {\n const params: Record<string, unknown> = {};\n if (metadata.params) {\n for (const p of metadata.params) {\n if (p.default !== undefined) {\n params[p.name] = resolveDynamicDefault(p.default);\n }\n }\n }\n\n // Expand date presets into start_date/end_date\n if (typeof params.date_range === 'string' && isDatePreset(params.date_range)) {\n const range = expandDatePreset(params.date_range);\n if (range) {\n params.start_date = range.start_date;\n params.end_date = range.end_date;\n }\n }\n\n const context = createTemplateContext(params, refs);\n return renderTemplate(sql, context);\n}\n\ninterface LoadedModel {\n name: string;\n sql: string;\n metadata: ModelMetadata;\n}\n\nasync function loadModels(dir: string): Promise<LoadedModel[]> {\n const models: LoadedModel[] = [];\n\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return models;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n const subModels = await loadModels(fullPath);\n models.push(...subModels);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n try {\n const parsed = parseModelMetadata(content);\n models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });\n } catch {\n // Skip unparseable models\n }\n }\n }\n\n return models;\n}\n\nexport function formatTestOutput(result: TestResult, connectionName: string): void {\n const { suite } = result;\n\n const testedModels = suite.models.filter(\n (m) => m.schemaCheck || m.assertions.length > 0 || m.error,\n );\n\n output.header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);\n\n for (const model of suite.models) {\n const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;\n if (!hasChecks) continue;\n\n console.log(` ${model.modelName}`);\n\n if (model.error) {\n output.error(` error: ${model.error}`);\n continue;\n }\n\n if (model.schemaCheck) {\n formatSchemaCheck(model.schemaCheck);\n }\n\n for (const assertion of model.assertions) {\n formatAssertion(assertion);\n }\n\n output.newline();\n }\n\n // Summary line\n const totalChecks = suite.passed + suite.failed;\n const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;\n const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : '';\n const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;\n\n if (suite.failed === 0) {\n output.success(`${summary}${skippedSuffix}${durationSuffix}`);\n } else {\n output.error(`${summary}${skippedSuffix}${durationSuffix}`);\n }\n}\n\nfunction formatSchemaCheck(check: NonNullable<TestSuiteResult['models'][0]['schemaCheck']>): void {\n if (check.passed) {\n output.success(` schema: returns expected columns (${check.expectedColumns.join(', ')})`);\n return;\n }\n\n const issues: string[] = [];\n if (check.missingColumns.length > 0) {\n issues.push(`missing: ${check.missingColumns.join(', ')}`);\n }\n for (const m of check.typeMismatches) {\n issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);\n }\n output.error(` schema: ${issues.join('; ')}`);\n}\n\nfunction formatAssertion(assertion: TestSuiteResult['models'][0]['assertions'][0]): void {\n const label = extractAssertionLabel(assertion.sql);\n\n if (assertion.warning) {\n output.warning(` ${label}`);\n output.detail(` ${assertion.warning}`);\n }\n\n if (assertion.error) {\n output.error(` test: ${label} -- error`);\n output.detail(` ${assertion.error}`);\n } else if (assertion.passed) {\n output.success(` test: ${label}`);\n } else {\n output.error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);\n if (assertion.sampleViolations) {\n for (const row of assertion.sampleViolations.slice(0, 3)) {\n output.detail(` ${JSON.stringify(row)}`);\n }\n }\n }\n}\n\nfunction extractAssertionLabel(sql: string): string {\n const whereMatch = sql.match(/WHERE\\s+(.+?)$/i);\n if (whereMatch?.[1]) {\n const clause = whereMatch[1].trim();\n return clause.length > 60 ? clause.slice(0, 57) + '...' : clause;\n }\n return sql.length > 60 ? sql.slice(0, 57) + '...' : sql;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yamchart",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Git-native business intelligence dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"tsup": "^8.0.0",
|
|
72
72
|
"typescript": "^5.7.0",
|
|
73
73
|
"vitest": "^2.1.0",
|
|
74
|
+
"@yamchart/advisor": "0.1.0",
|
|
74
75
|
"@yamchart/auth-local": "0.1.0",
|
|
75
76
|
"@yamchart/chat": "0.1.0",
|
|
76
77
|
"@yamchart/config": "0.1.2",
|
|
77
|
-
"@yamchart/advisor": "0.1.0",
|
|
78
78
|
"@yamchart/query": "0.1.2",
|
|
79
79
|
"@yamchart/schema": "0.1.2",
|
|
80
80
|
"@yamchart/server": "0.1.2"
|
|
@@ -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, CatalogJsonData } 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 // 4b. Load catalog.json (warehouse table metadata)\n let catalogJson: CatalogJsonData | null = null;\n try {\n const raw = await readFile(join(projectDir, '.yamchart', 'catalog.json'), 'utf-8');\n const parsed = JSON.parse(raw);\n catalogJson = { models: parsed.models || [] };\n } catch {\n // No catalog.json available\n }\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 catalogJson,\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":["../../../packages/auth-local/src/passwords.ts","../../../packages/auth-local/src/database.ts","../../../packages/auth-local/src/sessions.ts","../../../packages/auth-local/src/oauth-crypto.ts"],"sourcesContent":["import bcrypt from 'bcryptjs';\n\nconst SALT_ROUNDS = 12;\n\nexport async function hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, SALT_ROUNDS);\n}\n\nexport async function verifyPassword(password: string, hash: string): Promise<boolean> {\n return bcrypt.compare(password, hash);\n}\n","import Database from 'better-sqlite3';\nimport { randomBytes } from 'crypto';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { LocalUser, SafeUser, Role, Provider, Session, ShareLink, CreateShareLinkInput } from './types.js';\nimport type {\n OAuthClient, RegisterOAuthClientInput,\n OAuthAuthCode, CreateAuthCodeInput,\n OAuthToken, OAuthTokenType, CreateOAuthTokenInput,\n} from './oauth-types.js';\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n email TEXT UNIQUE NOT NULL,\n name TEXT NOT NULL,\n password_hash TEXT,\n role TEXT NOT NULL DEFAULT 'viewer',\n provider TEXT NOT NULL DEFAULT 'local',\n external_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE(provider, external_id)\n);\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS password_reset_tokens (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n token TEXT UNIQUE NOT NULL,\n expires_at TEXT NOT NULL,\n used INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS share_links (\n id TEXT PRIMARY KEY,\n token TEXT UNIQUE NOT NULL,\n resource_type TEXT NOT NULL,\n resource_name TEXT NOT NULL,\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n expires_at TEXT,\n is_active INTEGER NOT NULL DEFAULT 1\n);\n\nCREATE TABLE IF NOT EXISTS oauth_clients (\n client_id TEXT PRIMARY KEY,\n client_secret_hash TEXT,\n client_name TEXT,\n redirect_uris TEXT NOT NULL,\n grant_types TEXT NOT NULL,\n token_endpoint_auth_method TEXT NOT NULL DEFAULT 'none',\n scope TEXT,\n created_at TEXT NOT NULL,\n client_secret_expires_at INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS oauth_auth_codes (\n code_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n code_challenge_method TEXT NOT NULL,\n scope TEXT,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS oauth_tokens (\n token_hash TEXT PRIMARY KEY,\n token_type TEXT NOT NULL,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n scope TEXT,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n`;\n\nexport interface CreateUserInput {\n email: string;\n name: string;\n password_hash: string | null;\n role: Role;\n provider?: Provider;\n external_id?: string | null;\n}\n\nexport interface UpdateUserInput {\n name?: string;\n role?: Role;\n password_hash?: string;\n}\n\nfunction toSafeUser(user: LocalUser): SafeUser {\n return {\n id: user.id,\n email: user.email,\n name: user.name,\n role: user.role,\n provider: user.provider,\n attributes: user.attributes,\n created_at: user.created_at,\n updated_at: user.updated_at,\n };\n}\n\nexport class AuthDatabase {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.exec(SCHEMA_SQL);\n\n // Migrate: add attributes column if missing\n try {\n this.db.prepare(\"SELECT attributes FROM users LIMIT 0\").get();\n } catch {\n this.db.exec(\"ALTER TABLE users ADD COLUMN attributes TEXT NOT NULL DEFAULT '{}'\");\n }\n }\n\n close(): void {\n this.db.close();\n }\n\n needsSetup(): boolean {\n return this.getUserCount() === 0;\n }\n\n getUserCount(): number {\n const row = this.db.prepare('SELECT COUNT(*) as count FROM users').get() as { count: number };\n return row.count;\n }\n\n private getAdminCount(): number {\n const row = this.db.prepare(\"SELECT COUNT(*) as count FROM users WHERE role = 'admin'\").get() as { count: number };\n return row.count;\n }\n\n createUser(input: CreateUserInput): SafeUser {\n const id = uuidv4();\n const now = new Date().toISOString();\n\n this.db.prepare(`\n INSERT INTO users (id, email, name, password_hash, role, provider, external_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id,\n input.email,\n input.name,\n input.password_hash,\n input.role,\n input.provider ?? 'local',\n input.external_id ?? null,\n now,\n now,\n );\n\n return this.getUserById(id)! as SafeUser;\n }\n\n getUserById(id: string): LocalUser | null {\n return (this.db.prepare('SELECT * FROM users WHERE id = ?').get(id) as LocalUser) ?? null;\n }\n\n getUserByEmail(email: string): LocalUser | null {\n return (this.db.prepare('SELECT * FROM users WHERE email = ?').get(email) as LocalUser) ?? null;\n }\n\n listUsers(): SafeUser[] {\n const users = this.db.prepare('SELECT * FROM users ORDER BY created_at ASC').all() as LocalUser[];\n return users.map(toSafeUser);\n }\n\n updateUser(id: string, input: UpdateUserInput): void {\n // Guard: prevent changing last admin's role\n if (input.role && input.role !== 'admin') {\n const user = this.getUserById(id);\n if (user && user.role === 'admin' && this.getAdminCount() === 1) {\n throw new Error('Cannot change role of last admin user');\n }\n }\n\n const sets: string[] = [];\n const values: unknown[] = [];\n\n if (input.name !== undefined) { sets.push('name = ?'); values.push(input.name); }\n if (input.role !== undefined) { sets.push('role = ?'); values.push(input.role); }\n if (input.password_hash !== undefined) { sets.push('password_hash = ?'); values.push(input.password_hash); }\n\n if (sets.length === 0) return;\n\n sets.push('updated_at = ?');\n values.push(new Date().toISOString());\n values.push(id);\n\n this.db.prepare(`UPDATE users SET ${sets.join(', ')} WHERE id = ?`).run(...values);\n }\n\n deleteUser(id: string): void {\n const user = this.getUserById(id);\n if (user && user.role === 'admin' && this.getAdminCount() === 1) {\n throw new Error('Cannot delete last admin user');\n }\n this.db.prepare('DELETE FROM users WHERE id = ?').run(id);\n }\n\n createSession(token: string, userId: string, ttlMs: number): void {\n const now = new Date();\n const expiresAt = new Date(now.getTime() + ttlMs);\n\n this.db.prepare(`\n INSERT INTO sessions (id, user_id, expires_at, created_at)\n VALUES (?, ?, ?, ?)\n `).run(token, userId, expiresAt.toISOString(), now.toISOString());\n }\n\n validateSession(token: string): Session | null {\n const session = this.db.prepare(\n 'SELECT * FROM sessions WHERE id = ? AND expires_at > ?'\n ).get(token, new Date().toISOString()) as Session | undefined;\n\n return session ?? null;\n }\n\n deleteSession(token: string): void {\n this.db.prepare('DELETE FROM sessions WHERE id = ?').run(token);\n }\n\n deleteUserSessions(userId: string): void {\n this.db.prepare('DELETE FROM sessions WHERE user_id = ?').run(userId);\n }\n\n purgeExpiredSessions(): number {\n const result = this.db.prepare(\n 'DELETE FROM sessions WHERE expires_at <= ?'\n ).run(new Date().toISOString());\n return result.changes;\n }\n\n getUserByProviderAndExternalId(provider: string, externalId: string): LocalUser | null {\n return (this.db.prepare(\n 'SELECT * FROM users WHERE provider = ? AND external_id = ?'\n ).get(provider, externalId) as LocalUser) ?? null;\n }\n\n // --- User Attributes ---\n\n getUserAttributes(id: string): Record<string, string> {\n const row = this.db.prepare('SELECT attributes FROM users WHERE id = ?').get(id) as { attributes: string } | undefined;\n if (!row) return {};\n try {\n return JSON.parse(row.attributes);\n } catch {\n return {};\n }\n }\n\n updateUserAttributes(id: string, attributes: Record<string, string>): void {\n this.db.prepare('UPDATE users SET attributes = ?, updated_at = ? WHERE id = ?')\n .run(JSON.stringify(attributes), new Date().toISOString(), id);\n }\n\n // --- Share Links ---\n\n createShareLink(input: CreateShareLinkInput): ShareLink {\n const id = uuidv4();\n const token = randomBytes(32).toString('hex');\n const now = new Date().toISOString();\n\n this.db.prepare(`\n INSERT INTO share_links (id, token, resource_type, resource_name, created_by, created_at, expires_at, is_active)\n VALUES (?, ?, ?, ?, ?, ?, ?, 1)\n `).run(id, token, input.resourceType, input.resourceName, input.createdBy, now, input.expiresAt ?? null);\n\n return this.db.prepare('SELECT * FROM share_links WHERE id = ?').get(id) as ShareLink;\n }\n\n getShareLinkByToken(token: string): ShareLink | null {\n const now = new Date().toISOString();\n const link = this.db.prepare(\n 'SELECT * FROM share_links WHERE token = ? AND is_active = 1 AND (expires_at IS NULL OR expires_at > ?)'\n ).get(token, now) as ShareLink | undefined;\n return link ?? null;\n }\n\n listShareLinks(createdBy?: string): ShareLink[] {\n if (createdBy) {\n return this.db.prepare(\n 'SELECT * FROM share_links WHERE created_by = ? AND is_active = 1 ORDER BY created_at DESC'\n ).all(createdBy) as ShareLink[];\n }\n return this.db.prepare(\n 'SELECT * FROM share_links WHERE is_active = 1 ORDER BY created_at DESC'\n ).all() as ShareLink[];\n }\n\n revokeShareLink(id: string): void {\n this.db.prepare('UPDATE share_links SET is_active = 0 WHERE id = ?').run(id);\n }\n\n deleteShareLink(id: string): void {\n this.db.prepare('DELETE FROM share_links WHERE id = ?').run(id);\n }\n\n // ---- OAuth: clients ----\n registerOAuthClient(input: RegisterOAuthClientInput): OAuthClient {\n const now = new Date().toISOString();\n this.db.prepare(`\n INSERT INTO oauth_clients\n (client_id, client_secret_hash, client_name, redirect_uris, grant_types,\n token_endpoint_auth_method, scope, created_at, client_secret_expires_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n input.client_id,\n input.client_secret_hash,\n input.client_name,\n JSON.stringify(input.redirect_uris),\n JSON.stringify(input.grant_types),\n input.token_endpoint_auth_method,\n input.scope,\n now,\n input.client_secret_expires_at,\n );\n return this.getOAuthClient(input.client_id)!;\n }\n\n getOAuthClient(clientId: string): OAuthClient | null {\n return (this.db.prepare('SELECT * FROM oauth_clients WHERE client_id = ?').get(clientId) as OAuthClient) ?? null;\n }\n\n // ---- OAuth: authorization codes (single-use) ----\n createAuthCode(input: CreateAuthCodeInput): void {\n this.db.prepare(`\n INSERT INTO oauth_auth_codes\n (code_hash, client_id, user_id, redirect_uri, code_challenge, code_challenge_method, scope, expires_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n input.code_hash, input.client_id, input.user_id, input.redirect_uri,\n input.code_challenge, input.code_challenge_method, input.scope,\n input.expires_at, new Date().toISOString(),\n );\n }\n\n /** Atomically fetch-and-delete a non-expired auth code. Returns null if missing/expired. */\n consumeAuthCode(codeHash: string): OAuthAuthCode | null {\n const tx = this.db.transaction((hash: string) => {\n const row = this.db.prepare(\n 'SELECT * FROM oauth_auth_codes WHERE code_hash = ?'\n ).get(hash) as OAuthAuthCode | undefined;\n this.db.prepare('DELETE FROM oauth_auth_codes WHERE code_hash = ?').run(hash);\n if (!row) return null;\n if (new Date(row.expires_at).getTime() <= Date.now()) return null;\n return row;\n });\n return tx(codeHash);\n }\n\n // ---- OAuth: tokens ----\n createOAuthToken(input: CreateOAuthTokenInput): void {\n this.db.prepare(`\n INSERT INTO oauth_tokens (token_hash, token_type, client_id, user_id, scope, expires_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `).run(\n input.token_hash, input.token_type, input.client_id, input.user_id,\n input.scope, input.expires_at, new Date().toISOString(),\n );\n }\n\n getOAuthToken(tokenHash: string, type: OAuthTokenType): OAuthToken | null {\n const row = this.db.prepare(\n 'SELECT * FROM oauth_tokens WHERE token_hash = ? AND token_type = ? AND expires_at > ?'\n ).get(tokenHash, type, new Date().toISOString()) as OAuthToken | undefined;\n return row ?? null;\n }\n\n deleteOAuthToken(tokenHash: string): void {\n this.db.prepare('DELETE FROM oauth_tokens WHERE token_hash = ?').run(tokenHash);\n }\n\n purgeExpiredOAuth(): number {\n const now = new Date().toISOString();\n const codes = this.db.prepare('DELETE FROM oauth_auth_codes WHERE expires_at <= ?').run(now);\n const tokens = this.db.prepare('DELETE FROM oauth_tokens WHERE expires_at <= ?').run(now);\n return codes.changes + tokens.changes;\n }\n}\n","import { randomBytes } from 'crypto';\n\nexport function generateSessionToken(): string {\n return randomBytes(32).toString('hex');\n}\n\nconst DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days\n\nexport function parseTtl(ttl: string): number {\n const match = ttl.match(/^(\\d+)(d|h|m)$/);\n if (!match || !match[1] || !match[2]) return DEFAULT_TTL_MS;\n\n const value = parseInt(match[1], 10);\n switch (match[2]) {\n case 'd': return value * 24 * 60 * 60 * 1000;\n case 'h': return value * 60 * 60 * 1000;\n case 'm': return value * 60 * 1000;\n default: return DEFAULT_TTL_MS;\n }\n}\n","import { randomBytes, createHash } from 'node:crypto';\n\n/** A 256-bit opaque token, hex-encoded. */\nexport function generateOpaqueToken(): string {\n return randomBytes(32).toString('hex');\n}\n\n/** SHA-256 of a token, hex — what we store at rest so a DB leak yields no usable tokens. */\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n"],"mappings":"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","names":["randomBytes","randomBytes"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/connection-utils.ts"],"sourcesContent":["import { readFile, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n DuckDBConnector,\n PostgresConnector,\n MySQLConnector,\n SQLiteConnector,\n SnowflakeConnector,\n BigQueryConnector,\n resolvePostgresAuth,\n resolveMySQLAuth,\n resolveSnowflakeAuth,\n resolveBigQueryAuth,\n type Connector,\n type QueryResult,\n} from '@yamchart/query';\nimport {\n ConnectionSchema,\n type Connection,\n type DuckDBConnection,\n type PostgresConnection,\n type MySQLConnection,\n type SQLiteConnection,\n type SnowflakeConnection,\n type BigQueryConnection,\n} from '@yamchart/schema';\n\nexport interface ProjectConfig {\n name: string;\n version?: string;\n default_connection?: string;\n defaults?: {\n connection?: string;\n };\n}\n\n/**\n * Read and parse yamchart.yaml from a project directory.\n * Resolves environment overrides and merges yamchart.local.yaml if present.\n */\nexport async function loadProjectConfig(\n projectDir: string,\n env?: string,\n): Promise<ProjectConfig> {\n const configPath = join(projectDir, 'yamchart.yaml');\n const content = await readFile(configPath, 'utf-8');\n const raw = parseYaml(content) as Record<string, unknown>;\n\n const { resolveProjectConfig, deepMerge } = await import('@yamchart/schema');\n const resolvedEnv = env || process.env.YAMCHART_ENV || undefined;\n let merged = resolveProjectConfig(raw, resolvedEnv);\n\n // Merge yamchart.local.yaml if it exists\n const localPath = join(projectDir, 'yamchart.local.yaml');\n try {\n await access(localPath);\n const localContent = await readFile(localPath, 'utf-8');\n const localOverrides = parseYaml(localContent) as Record<string, unknown>;\n if (localOverrides && typeof localOverrides === 'object') {\n merged = deepMerge(merged, localOverrides);\n }\n } catch {\n // No local overrides file\n }\n\n return merged as ProjectConfig;\n}\n\n/**\n * Load and validate a connection YAML file.\n * Tries .yaml then .yml extensions.\n * If no overrideName, falls back to defaults.connection from project config.\n */\nexport async function resolveConnection(\n projectDir: string,\n overrideName?: string,\n env?: string,\n): Promise<Connection> {\n const config = await loadProjectConfig(projectDir, env);\n const connName = overrideName || config.defaults?.connection || config.default_connection;\n\n if (!connName) {\n throw new Error(\n 'No connection specified. Use --connection or set defaults.connection in yamchart.yaml.',\n );\n }\n\n const yamlPath = join(projectDir, 'connections', `${connName}.yaml`);\n const ymlPath = join(projectDir, 'connections', `${connName}.yml`);\n\n let connPath = yamlPath;\n try {\n await access(yamlPath);\n } catch {\n try {\n await access(ymlPath);\n connPath = ymlPath;\n } catch {\n throw new Error(`Connection \"${connName}\" not found at connections/${connName}.yaml`);\n }\n }\n\n const content = await readFile(connPath, 'utf-8');\n const raw = parseYaml(content);\n const parsed = ConnectionSchema.parse(raw);\n return parsed;\n}\n\n/**\n * Instantiate the right connector class based on connection.type.\n * Resolves auth and relative paths. Does NOT call connect().\n */\nexport function createConnector(connection: Connection, projectDir: string): Connector {\n switch (connection.type) {\n case 'duckdb': {\n const duckdbConn = connection as DuckDBConnection;\n const dbPath = duckdbConn.config.path.startsWith('/')\n ? duckdbConn.config.path\n : duckdbConn.config.path === ':memory:'\n ? ':memory:'\n : join(projectDir, duckdbConn.config.path);\n return new DuckDBConnector({ path: dbPath });\n }\n\n case 'postgres': {\n const pgConn = connection as PostgresConnection;\n const credentials = resolvePostgresAuth(pgConn);\n return new PostgresConnector({\n host: pgConn.config.host,\n port: pgConn.config.port,\n database: pgConn.config.database,\n schema: pgConn.config.schema,\n ssl: pgConn.config.ssl,\n user: credentials.user,\n password: credentials.password,\n min: pgConn.pool?.min_connections,\n max: pgConn.pool?.max_connections,\n idleTimeoutMillis: pgConn.pool?.idle_timeout,\n statementTimeout: pgConn.query?.timeout,\n });\n }\n\n case 'mysql': {\n const mysqlConn = connection as MySQLConnection;\n const credentials = resolveMySQLAuth(mysqlConn);\n return new MySQLConnector({\n host: mysqlConn.config.host,\n port: mysqlConn.config.port,\n database: mysqlConn.config.database,\n ssl: mysqlConn.config.ssl,\n user: credentials.user,\n password: credentials.password,\n min: mysqlConn.pool?.min_connections,\n max: mysqlConn.pool?.max_connections,\n idleTimeoutMillis: mysqlConn.pool?.idle_timeout,\n statementTimeout: mysqlConn.query?.timeout,\n });\n }\n\n case 'sqlite': {\n const sqliteConn = connection as SQLiteConnection;\n const dbPath = sqliteConn.config.path.startsWith('/')\n ? sqliteConn.config.path\n : sqliteConn.config.path === ':memory:'\n ? ':memory:'\n : join(projectDir, sqliteConn.config.path);\n return new SQLiteConnector({ path: dbPath });\n }\n\n case 'snowflake': {\n const sfConn = connection as SnowflakeConnection;\n const credentials = resolveSnowflakeAuth(sfConn);\n return new SnowflakeConnector({\n account: sfConn.config.account,\n username: credentials.username,\n password: credentials.password,\n privateKey: credentials.privateKey,\n authenticator: credentials.authenticator,\n clientStoreTemporaryCredential: credentials.clientStoreTemporaryCredential,\n warehouse: sfConn.config.warehouse,\n database: sfConn.config.database,\n schema: sfConn.config.schema,\n role: sfConn.config.role,\n statementTimeout: sfConn.query?.timeout,\n });\n }\n\n case 'bigquery': {\n const bqConn = connection as BigQueryConnection;\n const creds = resolveBigQueryAuth(bqConn);\n // Resolve a relative key_file against the project dir (like duckdb/sqlite paths).\n const keyFilename = creds.keyFilename\n ? (creds.keyFilename.startsWith('/') ? creds.keyFilename : join(projectDir, creds.keyFilename))\n : undefined;\n return new BigQueryConnector({\n projectId: bqConn.config.project_id,\n dataset: bqConn.config.dataset,\n location: bqConn.config.location,\n maxBytesBilled: bqConn.config.max_bytes_billed,\n credentials: creds.credentials,\n keyFilename,\n });\n }\n\n default:\n throw new Error(`Unsupported connection type: ${(connection as Connection).type}`);\n }\n}\n\n/**\n * Format a query result as an aligned terminal table.\n * Truncates values longer than 40 characters.\n */\nexport function formatTable(result: QueryResult): string {\n const { columns, rows, rowCount, durationMs } = result;\n const maxWidth = 40;\n\n const truncate = (val: unknown): string => {\n if (val === null || val === undefined) return 'NULL';\n const str = String(val);\n if (str.length > maxWidth) return str.slice(0, maxWidth - 3) + '...';\n return str;\n };\n\n // Calculate column widths\n const headers = columns.map((c) => c.name);\n const formattedRows = rows.map((row) =>\n columns.map((c) => truncate(row[c.name])),\n );\n\n const colWidths = headers.map((h, i) => {\n const dataMax = formattedRows.reduce(\n (max, row) => Math.max(max, row[i]!.length),\n 0,\n );\n return Math.max(h.length, dataMax);\n });\n\n // Build lines\n const lines: string[] = [];\n\n // Header\n const headerLine = headers.map((h, i) => h.padEnd(colWidths[i]!)).join(' ');\n lines.push(headerLine);\n\n // Separator\n const separator = colWidths.map((w) => '-'.repeat(w)).join(' ');\n lines.push(separator);\n\n // Data rows\n for (const row of formattedRows) {\n const line = row.map((val, i) => val.padEnd(colWidths[i]!)).join(' ');\n lines.push(line);\n }\n\n // Footer\n lines.push('');\n lines.push(`${rowCount} row(s) (${Math.round(durationMs)}ms)`);\n\n return lines.join('\\n');\n}\n\n/**\n * Format a query result as a JSON string.\n */\nexport function formatJSON(result: QueryResult): string {\n return JSON.stringify(\n {\n columns: result.columns,\n rows: result.rows,\n rowCount: result.rowCount,\n durationMs: result.durationMs,\n },\n null,\n 2,\n );\n}\n"],"mappings":"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","names":[]}
|