@kaelio/ktx 0.14.0 → 0.15.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.
Files changed (32) hide show
  1. package/assets/python/{kaelio_ktx-0.14.0-py3-none-any.whl → kaelio_ktx-0.15.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/setup-commands.js +1 -0
  5. package/dist/context/ingest/adapters/sigma/chunk.d.ts +6 -0
  6. package/dist/context/ingest/adapters/sigma/chunk.js +119 -0
  7. package/dist/context/ingest/adapters/sigma/client-port.d.ts +45 -0
  8. package/dist/context/ingest/adapters/sigma/client-port.js +1 -0
  9. package/dist/context/ingest/adapters/sigma/client.d.ts +33 -0
  10. package/dist/context/ingest/adapters/sigma/client.js +176 -0
  11. package/dist/context/ingest/adapters/sigma/detect.d.ts +1 -0
  12. package/dist/context/ingest/adapters/sigma/detect.js +23 -0
  13. package/dist/context/ingest/adapters/sigma/fetch.d.ts +14 -0
  14. package/dist/context/ingest/adapters/sigma/fetch.js +191 -0
  15. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.d.ts +17 -0
  16. package/dist/context/ingest/adapters/sigma/local-sigma.adapter.js +41 -0
  17. package/dist/context/ingest/adapters/sigma/project.d.ts +8 -0
  18. package/dist/context/ingest/adapters/sigma/project.js +186 -0
  19. package/dist/context/ingest/adapters/sigma/sigma.adapter.d.ts +18 -0
  20. package/dist/context/ingest/adapters/sigma/sigma.adapter.js +43 -0
  21. package/dist/context/ingest/adapters/sigma/types.d.ts +80 -0
  22. package/dist/context/ingest/adapters/sigma/types.js +82 -0
  23. package/dist/context/ingest/local-adapters.d.ts +2 -1
  24. package/dist/context/ingest/local-adapters.js +22 -0
  25. package/dist/context/project/config.d.ts +30 -0
  26. package/dist/context/project/driver-schemas.d.ts +15 -0
  27. package/dist/context/project/driver-schemas.js +39 -0
  28. package/dist/public-ingest.js +1 -0
  29. package/dist/setup-sources.d.ts +2 -1
  30. package/dist/setup-sources.js +84 -0
  31. package/dist/skills/sigma_ingest/SKILL.md +189 -0
  32. package/package.json +1 -1
@@ -0,0 +1,41 @@
1
+ import { resolveKtxConfigReference } from '../../../core/config-reference.js';
2
+ import { DEFAULT_SIGMA_CLIENT_CONFIG, DefaultSigmaClient } from './client.js';
3
+ import { SigmaSourceAdapter } from './sigma.adapter.js';
4
+ function stringField(value) {
5
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
6
+ }
7
+ export function sigmaRuntimeConfigFromLocalConnection(connectionId, connection, env = process.env) {
8
+ if (!connection || String(connection.driver).toLowerCase() !== 'sigma') {
9
+ throw new Error(`Connection "${connectionId}" is not a Sigma connection`);
10
+ }
11
+ const apiUrl = stringField(connection.api_url) ?? 'https://api.sigmacomputing.com';
12
+ const clientId = stringField(connection.client_id);
13
+ const literalSecret = stringField(connection.client_secret);
14
+ const secretRef = stringField(connection.client_secret_ref);
15
+ const clientSecret = literalSecret ?? (secretRef ? (resolveKtxConfigReference(secretRef, env) ?? null) : null);
16
+ if (!clientId) {
17
+ throw new Error(`Connection "${connectionId}" is missing Sigma client_id`);
18
+ }
19
+ if (!clientSecret) {
20
+ throw new Error(`Connection "${connectionId}" is missing Sigma client_secret or client_secret_ref`);
21
+ }
22
+ return { apiUrl, clientId, clientSecret };
23
+ }
24
+ class LocalSigmaClientFactory {
25
+ project;
26
+ options;
27
+ constructor(project, options) {
28
+ this.project = project;
29
+ this.options = options;
30
+ }
31
+ createClient(config, _ctx) {
32
+ const runtimeConfig = sigmaRuntimeConfigFromLocalConnection(config.sigmaConnectionId, this.project.config.connections[config.sigmaConnectionId], this.options.env);
33
+ return new DefaultSigmaClient(runtimeConfig, this.options.defaultClientConfig ?? DEFAULT_SIGMA_CLIENT_CONFIG);
34
+ }
35
+ }
36
+ export function createLocalSigmaSourceAdapter(project, options = {}) {
37
+ return new SigmaSourceAdapter({
38
+ clientFactory: new LocalSigmaClientFactory(project, options),
39
+ ...(options.logger ? { logger: options.logger } : {}),
40
+ });
41
+ }
@@ -0,0 +1,8 @@
1
+ import type { SemanticLayerService } from '../../../../context/sl/semantic-layer.service.js';
2
+ import type { DeterministicProjectionContext, ProjectionResult } from '../../types.js';
3
+ type SlService = Pick<SemanticLayerService, 'writeSource'> & {
4
+ forWorktree(workdir: string): Pick<SemanticLayerService, 'writeSource'>;
5
+ };
6
+ /** @internal */
7
+ export declare function projectSigmaDataModels(ctx: DeterministicProjectionContext, slService: SlService): Promise<ProjectionResult>;
8
+ export {};
@@ -0,0 +1,186 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { sigmaProjectionConfigSchema, stagedDataModelFileSchema, STAGED_FILES } from './types.js';
5
+ async function readProjectionConfig(stagedDir) {
6
+ try {
7
+ const body = await readFile(join(stagedDir, STAGED_FILES.projectionConfig), 'utf-8');
8
+ return sigmaProjectionConfigSchema.parse(JSON.parse(body)).connectionMappings;
9
+ }
10
+ catch {
11
+ return {};
12
+ }
13
+ }
14
+ const SIGMA_AUTHOR = { name: 'Sigma', email: 'system@kaelio.dev' };
15
+ // Best-effort schema for the raw spec blob stored in staged data model files.
16
+ const warehouseTableSourceSchema = z.object({
17
+ kind: z.literal('warehouse-table'),
18
+ connectionId: z.string(),
19
+ path: z.array(z.string()),
20
+ });
21
+ const specColumnSchema = z
22
+ .object({
23
+ id: z.string(),
24
+ formula: z.string().optional(),
25
+ name: z.string().optional(),
26
+ hidden: z.boolean().optional(),
27
+ description: z.string().optional(),
28
+ format: z.object({ kind: z.string() }).passthrough().optional(),
29
+ })
30
+ .passthrough();
31
+ const specElementSchema = z
32
+ .object({
33
+ id: z.string(),
34
+ kind: z.string().optional(),
35
+ name: z.string().optional(),
36
+ hidden: z.boolean().optional(),
37
+ source: z.object({ kind: z.string() }).passthrough().optional(),
38
+ columns: z.array(specColumnSchema).optional(),
39
+ })
40
+ .passthrough();
41
+ const specPageSchema = z
42
+ .object({
43
+ id: z.string(),
44
+ name: z.string().optional(),
45
+ elements: z.array(specElementSchema).optional(),
46
+ })
47
+ .passthrough();
48
+ const sigmaSpecSchema = z
49
+ .object({
50
+ name: z.string().optional(),
51
+ pages: z.array(specPageSchema).optional(),
52
+ })
53
+ .passthrough();
54
+ /** Extract the column name from a bracket formula like `[TABLE/Column Name]` or `[Column]`. */
55
+ function extractColumnName(formula) {
56
+ const match = /\[(?:[^\]/]+\/)?([^\]]+)\]/.exec(formula.trim());
57
+ return match?.[1] ?? null;
58
+ }
59
+ function slugify(value) {
60
+ return value
61
+ .toLowerCase()
62
+ .replace(/[^a-z0-9]+/g, '_')
63
+ .replace(/^_+|_+$/g, '');
64
+ }
65
+ function inferColumnType(col) {
66
+ const kind = col.format?.kind;
67
+ if (kind === 'datetime' || kind === 'date')
68
+ return 'time';
69
+ if (kind === 'number' || kind === 'currency' || kind === 'percent')
70
+ return 'number';
71
+ return 'string';
72
+ }
73
+ function buildSourceFromElement(dataModelName, elementName, elementId, warehousePath, columns) {
74
+ const table = warehousePath.join('.');
75
+ if (!table)
76
+ return null;
77
+ const modelSlug = slugify(dataModelName || elementId);
78
+ const elemSlug = elementName ? slugify(elementName) : '';
79
+ const sourceName = elemSlug && elemSlug !== modelSlug ? `${modelSlug}_${elemSlug}` : modelSlug;
80
+ if (!sourceName)
81
+ return null;
82
+ const slColumns = [];
83
+ for (const col of columns) {
84
+ if (col.hidden)
85
+ continue;
86
+ if (!col.formula)
87
+ continue;
88
+ // Aggregation formulas (Sum, Count, etc.) are Sigma-specific expressions that don't map to
89
+ // warehouse columns — skip them silently. The sigma_ingest skill surfaces them as wiki candidates.
90
+ if (/^[A-Za-z]+\(/.test(col.formula.trim()))
91
+ continue;
92
+ const displayName = col.name ?? extractColumnName(col.formula);
93
+ if (!displayName)
94
+ continue;
95
+ const colSlug = slugify(displayName);
96
+ if (!colSlug)
97
+ continue;
98
+ slColumns.push({
99
+ name: colSlug,
100
+ type: inferColumnType(col),
101
+ ...(col.description ? { descriptions: { user: col.description } } : {}),
102
+ });
103
+ }
104
+ const source = {
105
+ name: sourceName,
106
+ table,
107
+ grain: [],
108
+ columns: slColumns,
109
+ joins: [],
110
+ measures: [],
111
+ };
112
+ if (dataModelName) {
113
+ source.descriptions = { user: dataModelName };
114
+ }
115
+ return source;
116
+ }
117
+ /** @internal */
118
+ export async function projectSigmaDataModels(ctx, slService) {
119
+ const svc = ctx.workdir ? slService.forWorktree(ctx.workdir) : slService;
120
+ const warnings = [];
121
+ const errors = [];
122
+ const touchedSources = [];
123
+ const connectionMappings = await readProjectionConfig(ctx.stagedDir);
124
+ const dmDir = join(ctx.stagedDir, STAGED_FILES.dataModelsDir);
125
+ let entries;
126
+ try {
127
+ entries = await readdir(dmDir);
128
+ }
129
+ catch {
130
+ return { warnings, errors, touchedSources, changedWikiPageKeys: [] };
131
+ }
132
+ for (const entry of entries) {
133
+ if (!entry.endsWith('.json'))
134
+ continue;
135
+ let stagedFile;
136
+ try {
137
+ const body = await readFile(join(dmDir, entry), 'utf-8');
138
+ stagedFile = stagedDataModelFileSchema.parse(JSON.parse(body));
139
+ }
140
+ catch {
141
+ warnings.push(`Skipping malformed staged file: ${entry}`);
142
+ continue;
143
+ }
144
+ if (!stagedFile.spec)
145
+ continue;
146
+ let spec;
147
+ try {
148
+ spec = sigmaSpecSchema.parse(stagedFile.spec);
149
+ }
150
+ catch {
151
+ warnings.push(`Skipping unparseable spec for data model "${stagedFile.name}"`);
152
+ continue;
153
+ }
154
+ for (const page of spec.pages ?? []) {
155
+ for (const element of page.elements ?? []) {
156
+ if (element.hidden)
157
+ continue;
158
+ const warehouseSource = warehouseTableSourceSchema.safeParse(element.source);
159
+ if (!warehouseSource.success)
160
+ continue;
161
+ const source = buildSourceFromElement(stagedFile.name, element.name, element.id, warehouseSource.data.path, element.columns ?? []);
162
+ if (!source)
163
+ continue;
164
+ // Only write SL sources for elements whose Sigma connection is mapped to a warehouse connection.
165
+ // Writing under an unmapped connection produces gate failures because the Sigma connection
166
+ // is not a warehouse connection and cannot be validated.
167
+ const targetConnectionId = connectionMappings[warehouseSource.data.connectionId];
168
+ if (!targetConnectionId) {
169
+ warnings.push(`Skipping SL source for "${stagedFile.name}" / "${element.name ?? element.id}": ` +
170
+ `no connectionMappings entry for Sigma connection ${warehouseSource.data.connectionId}. ` +
171
+ `Add a connectionMappings entry in ktx.yaml to enable SL projection for this element.`);
172
+ continue;
173
+ }
174
+ try {
175
+ const result = await svc.writeSource(targetConnectionId, source, SIGMA_AUTHOR.name, SIGMA_AUTHOR.email, `Sigma: import data model "${stagedFile.name}"`);
176
+ touchedSources.push({ connectionId: targetConnectionId, sourceName: source.name });
177
+ warnings.push(...result.warnings);
178
+ }
179
+ catch (err) {
180
+ errors.push(`Failed to write source "${source.name}": ${err instanceof Error ? err.message : String(err)}`);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ return { warnings, errors, touchedSources, changedWikiPageKeys: [] };
186
+ }
@@ -0,0 +1,18 @@
1
+ import type { ChunkResult, DeterministicProjectionContext, DiffSet, FetchContext, ProjectionResult, SourceAdapter } from '../../types.js';
2
+ import type { SigmaClientFactory } from './client-port.js';
3
+ import { type SigmaFetchLogger } from './fetch.js';
4
+ export interface SigmaSourceAdapterDeps {
5
+ clientFactory: SigmaClientFactory;
6
+ logger?: SigmaFetchLogger;
7
+ }
8
+ export declare class SigmaSourceAdapter implements SourceAdapter {
9
+ private readonly deps;
10
+ readonly source = "sigma";
11
+ readonly skillNames: string[];
12
+ constructor(deps: SigmaSourceAdapterDeps);
13
+ detect(stagedDir: string): Promise<boolean>;
14
+ fetch(pullConfig: unknown, stagedDir: string, ctx: FetchContext): Promise<void>;
15
+ chunk(stagedDir: string, diffSet?: DiffSet): Promise<ChunkResult>;
16
+ listTargetConnectionIds(stagedDir: string): Promise<string[]>;
17
+ project(ctx: DeterministicProjectionContext): Promise<ProjectionResult>;
18
+ }
@@ -0,0 +1,43 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { chunkSigmaStagedDir } from './chunk.js';
4
+ import { detectSigmaStagedDir } from './detect.js';
5
+ import { fetchSigmaBundle } from './fetch.js';
6
+ import { projectSigmaDataModels } from './project.js';
7
+ import { sigmaProjectionConfigSchema, STAGED_FILES } from './types.js';
8
+ export class SigmaSourceAdapter {
9
+ deps;
10
+ source = 'sigma';
11
+ skillNames = ['sigma_ingest'];
12
+ constructor(deps) {
13
+ this.deps = deps;
14
+ }
15
+ detect(stagedDir) {
16
+ return detectSigmaStagedDir(stagedDir);
17
+ }
18
+ async fetch(pullConfig, stagedDir, ctx) {
19
+ await fetchSigmaBundle({
20
+ pullConfig,
21
+ stagedDir,
22
+ ctx,
23
+ clientFactory: this.deps.clientFactory,
24
+ ...(this.deps.logger ? { logger: this.deps.logger } : {}),
25
+ });
26
+ }
27
+ chunk(stagedDir, diffSet) {
28
+ return chunkSigmaStagedDir(stagedDir, { diffSet });
29
+ }
30
+ async listTargetConnectionIds(stagedDir) {
31
+ try {
32
+ const body = await readFile(join(stagedDir, STAGED_FILES.projectionConfig), 'utf-8');
33
+ const config = sigmaProjectionConfigSchema.parse(JSON.parse(body));
34
+ return [...new Set(Object.values(config.connectionMappings))].sort();
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ }
40
+ project(ctx) {
41
+ return projectSigmaDataModels(ctx, ctx.semanticLayerService);
42
+ }
43
+ }
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ /** Filters applied when listing workbooks. Shared with ListWorkbooksOptions in client-port.ts. */
3
+ declare const workbookFilterSchema: z.ZodObject<{
4
+ includeArchived: z.ZodDefault<z.ZodBoolean>;
5
+ includeExplorations: z.ZodDefault<z.ZodBoolean>;
6
+ updatedSince: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ /** Input shape for listWorkbooks — all fields optional since the client applies its own defaults. */
9
+ export type WorkbookFilterInput = z.input<typeof workbookFilterSchema>;
10
+ /** The lean config the adapter needs at `fetch()` time, stored in the ingest job's `bundleRef.config`. */
11
+ declare const sigmaPullConfigSchema: z.ZodObject<{
12
+ sigmaConnectionId: z.ZodString;
13
+ connectionMappings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
14
+ workbookFilter: z.ZodDefault<z.ZodObject<{
15
+ includeArchived: z.ZodDefault<z.ZodBoolean>;
16
+ includeExplorations: z.ZodDefault<z.ZodBoolean>;
17
+ updatedSince: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$strip>>;
19
+ dataModelFilter: z.ZodOptional<z.ZodObject<{
20
+ updatedSince: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>>;
22
+ }, z.core.$strip>;
23
+ export type SigmaPullConfig = z.infer<typeof sigmaPullConfigSchema>;
24
+ export declare function parseSigmaPullConfig(raw: unknown): SigmaPullConfig;
25
+ /** Written to stagedDir during fetch() and read back by project(), listTargetConnectionIds(), and the sigma_ingest skill. */
26
+ export declare const sigmaProjectionConfigSchema: z.ZodObject<{
27
+ connectionMappings: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
28
+ workbookFilter: z.ZodDefault<z.ZodObject<{
29
+ includeArchived: z.ZodDefault<z.ZodBoolean>;
30
+ includeExplorations: z.ZodDefault<z.ZodBoolean>;
31
+ updatedSince: z.ZodOptional<z.ZodString>;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;
34
+ export type SigmaProjectionConfig = z.infer<typeof sigmaProjectionConfigSchema>;
35
+ /**
36
+ * A staged data model file, one per `data-models/<id>.json`.
37
+ * Stores the summary metadata plus the raw spec blob from GET /v2/dataModels/{id}/spec.
38
+ */
39
+ export declare const stagedDataModelFileSchema: z.ZodObject<{
40
+ sigmaId: z.ZodString;
41
+ name: z.ZodString;
42
+ path: z.ZodString;
43
+ latestVersion: z.ZodNumber;
44
+ updatedAt: z.ZodString;
45
+ isArchived: z.ZodDefault<z.ZodBoolean>;
46
+ dataModelUrlId: z.ZodOptional<z.ZodString>;
47
+ spec: z.ZodUnknown;
48
+ }, z.core.$strip>;
49
+ export type StagedDataModelFile = z.infer<typeof stagedDataModelFileSchema>;
50
+ /** The manifest written once per `fetch()`. Presence acts as the detect() sentinel. */
51
+ export declare const sigmaManifestSchema: z.ZodObject<{
52
+ sigmaConnectionId: z.ZodString;
53
+ fetchedAt: z.ZodString;
54
+ dataModelCount: z.ZodNumber;
55
+ workbookCount: z.ZodDefault<z.ZodNumber>;
56
+ }, z.core.$strip>;
57
+ export type SigmaManifest = z.infer<typeof sigmaManifestSchema>;
58
+ /**
59
+ * A staged workbook file, one per `workbooks/<id>.json`.
60
+ * Stores the summary metadata from GET /v2/workbooks (no separate spec endpoint).
61
+ */
62
+ export declare const stagedWorkbookFileSchema: z.ZodObject<{
63
+ sigmaId: z.ZodString;
64
+ name: z.ZodString;
65
+ path: z.ZodString;
66
+ latestVersion: z.ZodNumber;
67
+ updatedAt: z.ZodString;
68
+ isArchived: z.ZodDefault<z.ZodBoolean>;
69
+ workbookUrlId: z.ZodOptional<z.ZodString>;
70
+ description: z.ZodOptional<z.ZodString>;
71
+ }, z.core.$strip>;
72
+ export type StagedWorkbookFile = z.infer<typeof stagedWorkbookFileSchema>;
73
+ /** Filenames inside stagedDir. Centralized so chunk() + fetch() + detect() all agree. */
74
+ export declare const STAGED_FILES: {
75
+ readonly manifest: "sigma-manifest.json";
76
+ readonly projectionConfig: "sigma-projection-config.json";
77
+ readonly dataModelsDir: "data-models";
78
+ readonly workbooksDir: "workbooks";
79
+ };
80
+ export {};
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ const sigmaLocalConnectionIdSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/);
3
+ /** Filters applied when listing workbooks. Shared with ListWorkbooksOptions in client-port.ts. */
4
+ const workbookFilterSchema = z.object({
5
+ includeArchived: z.boolean().default(false),
6
+ includeExplorations: z.boolean().default(false),
7
+ /** ISO 8601 date string. Only workbooks updated on or after this date are included. */
8
+ updatedSince: z.string().optional(),
9
+ });
10
+ const dataModelFilterSchema = z.object({
11
+ /** ISO 8601 date string. Only data models updated on or after this date are fetched. */
12
+ updatedSince: z.string().optional(),
13
+ });
14
+ /** The lean config the adapter needs at `fetch()` time, stored in the ingest job's `bundleRef.config`. */
15
+ const sigmaPullConfigSchema = z.object({
16
+ /** The ktx connection ID for the Sigma instance being swept. */
17
+ sigmaConnectionId: sigmaLocalConnectionIdSchema,
18
+ /**
19
+ * Maps Sigma internal connection UUIDs (source.connectionId in data model specs)
20
+ * to ktx warehouse connection IDs. When present, projected semantic-layer sources
21
+ * are written under the mapped warehouse connection rather than the Sigma connection.
22
+ */
23
+ connectionMappings: z.record(z.string(), z.string()).optional(),
24
+ /** Filters applied when listing workbooks. Defaults exclude archived and exploration workbooks. */
25
+ workbookFilter: workbookFilterSchema.default({ includeArchived: false, includeExplorations: false }),
26
+ /** Filters applied when listing data models. */
27
+ dataModelFilter: dataModelFilterSchema.optional(),
28
+ });
29
+ export function parseSigmaPullConfig(raw) {
30
+ return sigmaPullConfigSchema.parse(raw);
31
+ }
32
+ /** Written to stagedDir during fetch() and read back by project(), listTargetConnectionIds(), and the sigma_ingest skill. */
33
+ export const sigmaProjectionConfigSchema = z.object({
34
+ connectionMappings: z.record(z.string(), z.string()).default({}),
35
+ /** Filters that were active when workbooks were last fetched. Tells the skill what the staged set covers. */
36
+ workbookFilter: workbookFilterSchema.default({ includeArchived: false, includeExplorations: false }),
37
+ });
38
+ /**
39
+ * A staged data model file, one per `data-models/<id>.json`.
40
+ * Stores the summary metadata plus the raw spec blob from GET /v2/dataModels/{id}/spec.
41
+ */
42
+ export const stagedDataModelFileSchema = z.object({
43
+ sigmaId: z.string(),
44
+ name: z.string(),
45
+ /** Full path in Sigma, e.g. "Finance/Revenue Model". */
46
+ path: z.string(),
47
+ latestVersion: z.number(),
48
+ updatedAt: z.string(),
49
+ isArchived: z.boolean().default(false),
50
+ /** URL-safe slug Sigma uses in the web UI (dataModelUrlId from the API). */
51
+ dataModelUrlId: z.string().optional(),
52
+ /** Raw spec from GET /v2/dataModels/{id}/spec (JSON format). */
53
+ spec: z.unknown(),
54
+ });
55
+ /** The manifest written once per `fetch()`. Presence acts as the detect() sentinel. */
56
+ export const sigmaManifestSchema = z.object({
57
+ sigmaConnectionId: sigmaLocalConnectionIdSchema,
58
+ fetchedAt: z.string(),
59
+ dataModelCount: z.number().int(),
60
+ workbookCount: z.number().int().default(0),
61
+ });
62
+ /**
63
+ * A staged workbook file, one per `workbooks/<id>.json`.
64
+ * Stores the summary metadata from GET /v2/workbooks (no separate spec endpoint).
65
+ */
66
+ export const stagedWorkbookFileSchema = z.object({
67
+ sigmaId: z.string(),
68
+ name: z.string(),
69
+ path: z.string(),
70
+ latestVersion: z.number(),
71
+ updatedAt: z.string(),
72
+ isArchived: z.boolean().default(false),
73
+ workbookUrlId: z.string().optional(),
74
+ description: z.string().optional(),
75
+ });
76
+ /** Filenames inside stagedDir. Centralized so chunk() + fetch() + detect() all agree. */
77
+ export const STAGED_FILES = {
78
+ manifest: 'sigma-manifest.json',
79
+ projectionConfig: 'sigma-projection-config.json',
80
+ dataModelsDir: 'data-models',
81
+ workbooksDir: 'workbooks',
82
+ };
@@ -7,6 +7,7 @@ import { type LookerMappingClient, type LookerTableIdentifierParser } from './ad
7
7
  import type { LookerRuntimeClient } from './adapters/looker/fetch.js';
8
8
  import type { MetabaseClientLogger } from './adapters/metabase/client.js';
9
9
  import type { MetabaseFetchLogger } from './adapters/metabase/fetch.js';
10
+ import type { SigmaFetchLogger } from './adapters/sigma/fetch.js';
10
11
  import type { NotionFetchLogger } from './adapters/notion/fetch.js';
11
12
  import type { SourceAdapter } from './types.js';
12
13
  export interface DefaultLocalIngestAdaptersOptions {
@@ -29,7 +30,7 @@ export interface DefaultLocalIngestAdaptersOptions {
29
30
  };
30
31
  logger?: LocalIngestOperationalLogger;
31
32
  }
32
- type LocalIngestOperationalLogger = MetabaseClientLogger & MetabaseFetchLogger & LookerClientLogger & NotionFetchLogger;
33
+ type LocalIngestOperationalLogger = MetabaseClientLogger & MetabaseFetchLogger & LookerClientLogger & NotionFetchLogger & SigmaFetchLogger;
33
34
  export declare function createDefaultLocalIngestAdapters(project: KtxLocalProject, options?: DefaultLocalIngestAdaptersOptions): SourceAdapter[];
34
35
  export declare function localPullConfigForAdapter(project: KtxLocalProject, adapter: SourceAdapter, connectionId: string, options?: DefaultLocalIngestAdaptersOptions): Promise<unknown>;
35
36
  export {};
@@ -22,6 +22,7 @@ import { buildLookerPullConfigFromInputs, } from './adapters/looker/mapping.js';
22
22
  import { LookmlSourceAdapter } from './adapters/lookml/lookml.adapter.js';
23
23
  import { pullConfigFromIntegrationConfig } from './adapters/lookml/pull-config.js';
24
24
  import { createLocalMetabaseSourceAdapter } from './adapters/metabase/local-metabase.adapter.js';
25
+ import { createLocalSigmaSourceAdapter } from './adapters/sigma/local-sigma.adapter.js';
25
26
  import { MetricflowSourceAdapter } from './adapters/metricflow/metricflow.adapter.js';
26
27
  import { pullConfigFromMetricflowIntegration } from './adapters/metricflow/pull-config.js';
27
28
  import { LocalNotionRuntimeStore } from './adapters/notion/local-state-store.js';
@@ -51,6 +52,9 @@ export function createDefaultLocalIngestAdapters(project, options = {}) {
51
52
  createLocalMetabaseSourceAdapter(project, {
52
53
  ...(options.logger ? { logger: options.logger } : {}),
53
54
  }),
55
+ createLocalSigmaSourceAdapter(project, {
56
+ ...(options.logger ? { logger: options.logger } : {}),
57
+ }),
54
58
  new GdriveSourceAdapter(),
55
59
  new LookerSourceAdapter({
56
60
  clientFactory: {
@@ -183,6 +187,24 @@ export async function localPullConfigForAdapter(project, adapter, connectionId,
183
187
  if (adapter.source === 'metabase') {
184
188
  throw new Error('Metabase scheduled pulls fan out by mapping. Call runLocalMetabaseIngest() or use `ktx ingest <metabase-source-id>` from the CLI.');
185
189
  }
190
+ if (adapter.source === 'sigma') {
191
+ const sigmaConn = project.config.connections[connectionId];
192
+ const connectionMappings = sigmaConn && 'connectionMappings' in sigmaConn && sigmaConn.connectionMappings != null
193
+ ? sigmaConn.connectionMappings
194
+ : undefined;
195
+ const workbookFilter = sigmaConn && 'workbookFilter' in sigmaConn && sigmaConn.workbookFilter != null
196
+ ? sigmaConn.workbookFilter
197
+ : undefined;
198
+ const dataModelFilter = sigmaConn && 'dataModelFilter' in sigmaConn && sigmaConn.dataModelFilter != null
199
+ ? sigmaConn.dataModelFilter
200
+ : undefined;
201
+ return {
202
+ sigmaConnectionId: connectionId,
203
+ ...(connectionMappings ? { connectionMappings } : {}),
204
+ ...(workbookFilter ? { workbookFilter } : {}),
205
+ ...(dataModelFilter ? { dataModelFilter } : {}),
206
+ };
207
+ }
186
208
  const connection = project.config.connections[connectionId];
187
209
  if (adapter.source === HISTORIC_SQL_SOURCE_KEY) {
188
210
  if (options.historicSqlPullConfigOverride) {
@@ -223,6 +223,21 @@ declare const connectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
223
223
  path: z.ZodOptional<z.ZodString>;
224
224
  auth_token_ref: z.ZodOptional<z.ZodString>;
225
225
  }, z.core.$loose>;
226
+ }, z.core.$loose>, z.ZodObject<{
227
+ driver: z.ZodLiteral<"sigma">;
228
+ api_url: z.ZodDefault<z.ZodString>;
229
+ client_id: z.ZodString;
230
+ client_secret: z.ZodOptional<z.ZodString>;
231
+ client_secret_ref: z.ZodOptional<z.ZodString>;
232
+ connectionMappings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
233
+ workbookFilter: z.ZodOptional<z.ZodObject<{
234
+ includeArchived: z.ZodDefault<z.ZodBoolean>;
235
+ includeExplorations: z.ZodDefault<z.ZodBoolean>;
236
+ updatedSince: z.ZodOptional<z.ZodString>;
237
+ }, z.core.$strip>>;
238
+ dataModelFilter: z.ZodOptional<z.ZodObject<{
239
+ updatedSince: z.ZodOptional<z.ZodString>;
240
+ }, z.core.$strip>>;
226
241
  }, z.core.$loose>], "driver">;
227
242
  declare const ktxProjectConfigSchema: z.ZodObject<{
228
243
  setup: z.ZodOptional<z.ZodObject<{
@@ -347,6 +362,21 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
347
362
  path: z.ZodOptional<z.ZodString>;
348
363
  auth_token_ref: z.ZodOptional<z.ZodString>;
349
364
  }, z.core.$loose>;
365
+ }, z.core.$loose>, z.ZodObject<{
366
+ driver: z.ZodLiteral<"sigma">;
367
+ api_url: z.ZodDefault<z.ZodString>;
368
+ client_id: z.ZodString;
369
+ client_secret: z.ZodOptional<z.ZodString>;
370
+ client_secret_ref: z.ZodOptional<z.ZodString>;
371
+ connectionMappings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
372
+ workbookFilter: z.ZodOptional<z.ZodObject<{
373
+ includeArchived: z.ZodDefault<z.ZodBoolean>;
374
+ includeExplorations: z.ZodDefault<z.ZodBoolean>;
375
+ updatedSince: z.ZodOptional<z.ZodString>;
376
+ }, z.core.$strip>>;
377
+ dataModelFilter: z.ZodOptional<z.ZodObject<{
378
+ updatedSince: z.ZodOptional<z.ZodString>;
379
+ }, z.core.$strip>>;
350
380
  }, z.core.$loose>], "driver">>>;
351
381
  storage: z.ZodPrefault<z.ZodObject<{
352
382
  state: z.ZodDefault<z.ZodEnum<{
@@ -118,4 +118,19 @@ export declare const connectionConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
118
118
  path: z.ZodOptional<z.ZodString>;
119
119
  auth_token_ref: z.ZodOptional<z.ZodString>;
120
120
  }, z.core.$loose>;
121
+ }, z.core.$loose>, z.ZodObject<{
122
+ driver: z.ZodLiteral<"sigma">;
123
+ api_url: z.ZodDefault<z.ZodString>;
124
+ client_id: z.ZodString;
125
+ client_secret: z.ZodOptional<z.ZodString>;
126
+ client_secret_ref: z.ZodOptional<z.ZodString>;
127
+ connectionMappings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
128
+ workbookFilter: z.ZodOptional<z.ZodObject<{
129
+ includeArchived: z.ZodDefault<z.ZodBoolean>;
130
+ includeExplorations: z.ZodDefault<z.ZodBoolean>;
131
+ updatedSince: z.ZodOptional<z.ZodString>;
132
+ }, z.core.$strip>>;
133
+ dataModelFilter: z.ZodOptional<z.ZodObject<{
134
+ updatedSince: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strip>>;
121
136
  }, z.core.$loose>], "driver">;
@@ -218,6 +218,44 @@ const metricflowConnectionSchema = z
218
218
  .describe('Nested MetricFlow configuration block.'),
219
219
  })
220
220
  .describe('MetricFlow / SL context-source connection.');
221
+ const sigmaConnectionSchema = z
222
+ .looseObject({
223
+ driver: z.literal('sigma'),
224
+ api_url: z
225
+ .string()
226
+ .url()
227
+ .default('https://api.sigmacomputing.com')
228
+ .describe('Sigma API base URL. Defaults to the GCP US endpoint; change for other regions.'),
229
+ client_id: z.string().min(1).describe('Sigma API client ID.'),
230
+ client_secret: z.string().min(1).optional().describe('Literal Sigma client secret. Prefer client_secret_ref.'),
231
+ client_secret_ref: z
232
+ .string()
233
+ .min(1)
234
+ .optional()
235
+ .describe('Reference to Sigma client secret (e.g. env:SIGMA_CLIENT_SECRET).'),
236
+ connectionMappings: z
237
+ .record(z.string(), z.string())
238
+ .optional()
239
+ .describe('Maps Sigma internal connection UUIDs to ktx warehouse connection IDs. ' +
240
+ 'When set, projected semantic-layer sources land under the mapped warehouse connection ' +
241
+ 'instead of the Sigma connection, enabling sl_validate. ' +
242
+ 'Find UUIDs in data model specs under source.connectionId.'),
243
+ workbookFilter: z
244
+ .object({
245
+ includeArchived: z.boolean().default(false),
246
+ includeExplorations: z.boolean().default(false),
247
+ updatedSince: z.string().optional().describe('ISO 8601 date string. Only workbooks updated on or after this date are ingested.'),
248
+ })
249
+ .optional()
250
+ .describe('Filters applied when listing workbooks during ingest. Defaults exclude archived and exploration workbooks.'),
251
+ dataModelFilter: z
252
+ .object({
253
+ updatedSince: z.string().optional().describe('ISO 8601 date string. Only data models updated on or after this date are fetched.'),
254
+ })
255
+ .optional()
256
+ .describe('Filters applied when listing data models during ingest.'),
257
+ })
258
+ .describe('Sigma Computing API connection for ingesting data models.');
221
259
  export const connectionConfigSchema = z.discriminatedUnion('driver', [
222
260
  ...warehouseConnectionSchemas,
223
261
  mongodbConnectionSchema,
@@ -228,4 +266,5 @@ export const connectionConfigSchema = z.discriminatedUnion('driver', [
228
266
  gdriveConnectionSchema,
229
267
  dbtConnectionSchema,
230
268
  metricflowConnectionSchema,
269
+ sigmaConnectionSchema,
231
270
  ]);
@@ -21,6 +21,7 @@ const sourceAdapterByDriver = new Map([
21
21
  ['metricflow', 'metricflow'],
22
22
  ['dbt', 'dbt'],
23
23
  ['lookml', 'lookml'],
24
+ ['sigma', 'sigma'],
24
25
  ]);
25
26
  export function publicProgressMessage(message, target) {
26
27
  let current = message;
@@ -3,7 +3,7 @@ import { type KtxProjectConnectionConfig } from './context/project/config.js';
3
3
  import type { KtxCliIo } from './cli-runtime.js';
4
4
  import { pickNotionRootPages } from './notion-page-picker.js';
5
5
  import { type KtxSetupPromptOption } from './setup-prompts.js';
6
- export type KtxSetupSourceType = 'dbt' | 'metricflow' | 'metabase' | 'looker' | 'lookml' | 'notion' | 'gdrive';
6
+ export type KtxSetupSourceType = 'dbt' | 'metricflow' | 'metabase' | 'looker' | 'lookml' | 'notion' | 'sigma' | 'gdrive';
7
7
  export interface KtxSetupSourcesArgs {
8
8
  projectDir: string;
9
9
  inputMode: 'auto' | 'disabled';
@@ -99,6 +99,7 @@ export interface KtxSetupSourcesDeps {
99
99
  validateLooker?: (projectDir: string, connectionId: string) => Promise<SourceValidationResult>;
100
100
  validateLookml?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
101
101
  validateNotion?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
102
+ validateSigma?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
102
103
  validateGdrive?: (connection: KtxProjectConnectionConfig) => Promise<SourceValidationResult>;
103
104
  pickNotionRootPages?: typeof pickNotionRootPages;
104
105
  discoverMetabaseDatabases?: (args: {