@bluemarble/bi 0.6.4 → 0.6.6

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-LoQzFAle.js","names":[],"sources":["../src/star.ts","../src/model.ts","../src/codegen.ts"],"sourcesContent":["/**\n * Modelo estrela (`tables` + `relationships`): tabelas fato e dimensão ligadas por relacionamentos\n * N:1, como no modelo do Power BI. Aqui ficam as regras do grafo — quem alcança quem e por qual\n * caminho —, usadas pela validação, pelo compilador e pelo diagrama do devtools.\n *\n * O filtro \"desce\" do lado 1 para o lado N: uma medida da fato `fPesagem` enxerga a dimensão\n * `dOperacao` se existe um caminho `fPesagem -> ... -> dOperacao` seguindo os relacionamentos no\n * sentido N -> 1 (snowflake vale: `fPesagem -> dOperacao -> dNavio`).\n */\n\nimport type { ModelDef, RelationshipDef } from './model'\n\nexport type ColumnRef = { table: string; column: string }\n\nexport type Edge = { from: ColumnRef; to: ColumnRef; index: number; inner: boolean }\n\n/** `'tabela.coluna'` -> `{ table, column }` (nulo se não tiver esse formato). */\nexport function parseColumnRef(ref: unknown): ColumnRef | null {\n if (typeof ref !== 'string') return null\n const match = /^([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(ref.trim())\n return match ? { table: match[1], column: match[2] } : null\n}\n\n/** Relacionamentos válidos do modelo, como arestas N -> 1. */\nexport function modelEdges(model: Pick<ModelDef, 'relationships'>): Edge[] {\n return (model.relationships ?? []).flatMap((relationship: RelationshipDef, index) => {\n const from = parseColumnRef(relationship?.from)\n const to = parseColumnRef(relationship?.to)\n return from && to ? [{ from, to, index, inner: relationship.inner === true }] : []\n })\n}\n\nconst escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Tabelas citadas no SQL como `tabela.coluna`. O que está entre aspas simples é ignorado, para\n * `'a.b'` num literal não contar como referência.\n */\nexport function referencedTables(sql: string, tables: string[]): string[] {\n const code = sql.replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n return tables.filter((name) => new RegExp(`(?<![\\\\w.\\`])${escapeRegex(name)}\\\\.`).test(code))\n}\n\n/** Passo de um caminho: entra em `table` pela aresta `edge` (de `edge.from.table`). */\nexport type PathStep = { table: string; edge: Edge }\n\nexport type Reach = {\n /** Tabela -> caminho mais curto a partir da origem (vazio para a própria origem). */\n paths: Map<string, PathStep[]>\n /**\n * Tabelas alcançadas por mais de um caminho (de qualquer tamanho): o filtro ficaria ambíguo. É a\n * mesma regra do Power BI, que só deixa um caminho ativo entre duas tabelas.\n */\n ambiguous: string[]\n}\n\n/** Busca em largura a partir de `start`, seguindo os relacionamentos no sentido N -> 1. */\nexport function reachFrom(start: string, edges: Edge[]): Reach {\n const paths = new Map<string, PathStep[]>([[start, []]])\n let frontier = [start]\n while (frontier.length) {\n const next: string[] = []\n for (const table of frontier) {\n for (const edge of edges) {\n if (edge.from.table !== table || paths.has(edge.to.table)) continue\n paths.set(edge.to.table, [...(paths.get(table) ?? []), { table: edge.to.table, edge }])\n next.push(edge.to.table)\n }\n }\n frontier = next\n }\n // Cada aresta saindo de uma tabela alcançada é um caminho até o destino dela\n const incoming = new Map<string, number>()\n for (const edge of edges)\n if (paths.has(edge.from.table))\n incoming.set(edge.to.table, (incoming.get(edge.to.table) ?? 0) + 1)\n const ambiguous = [...incoming]\n .filter(([table, count]) => table !== start && count > 1)\n .map(([table]) => table)\n return { paths, ambiguous }\n}\n\n/**\n * Tabelas que precisam entrar na consulta, na ordem do JOIN, para que a partir de `home` se chegue\n * a todas as `targets`. Como cada tabela é alcançada por um caminho só (o modelo valida ambiguidade),\n * a união dos caminhos é uma árvore: cada tabela entra uma vez, ligada pela aresta de quem veio antes.\n */\nexport function joinPlan(home: string, targets: Iterable<string>, edges: Edge[]): PathStep[] {\n const { paths } = reachFrom(home, edges)\n const plan: PathStep[] = []\n const added = new Set([home])\n const include = (target: string) => {\n for (const step of paths.get(target) ?? []) {\n if (added.has(step.table)) continue\n added.add(step.table)\n plan.push(step)\n }\n }\n for (const target of targets) include(target)\n // Relacionamento `inner` entra sempre que o lado N está na consulta: ele filtra mesmo sem\n // dimensão pedida (repete até não entrar mais nada, porque cada tabela nova pode ter os seus)\n for (let grew = true; grew;) {\n grew = false\n for (const edge of edges)\n if (edge.inner && added.has(edge.from.table) && !added.has(edge.to.table)) {\n include(edge.to.table)\n grew = true\n }\n }\n // Pai antes do filho: ordena pela profundidade do caminho\n const depth = (table: string) => paths.get(table)?.length ?? 0\n return plan.sort((a, b) => depth(a.table) - depth(b.table))\n}\n\n/**\n * Tabela de origem de uma medida `sum`: entre as tabelas que o SQL cita, a que alcança todas as\n * outras (ex.: `fPesagem.peso * dProduto.fator` agrega em `fPesagem`). Nulo se nenhuma alcança.\n */\nexport function measureHome(tables: string[], edges: Edge[]): string | null {\n if (!tables.length) return null\n return (\n tables.find((candidate) => {\n const { paths } = reachFrom(candidate, edges)\n return tables.every((table) => paths.has(table))\n }) ?? null\n )\n}\n","import { measureHome, modelEdges, parseColumnRef, reachFrom, referencedTables } from './star'\n\n/**\n * Tipos e validação do modelo semântico. Fica fora de `server/` porque o devtools (client) também\n * valida o rascunho editado antes de mandá-lo pro servidor.\n */\n\n/**\n * Dicionário: nome de negócio e explicação de cada campo. Os componentes usam como padrão (rótulo,\n * formatação) e o `ModelDictionary` vira a documentação do modelo para quem usa o dashboard.\n */\nexport type FieldMeta = {\n label?: string\n description?: string\n}\n\nexport const VALUE_FORMATS = ['number', 'integer', 'currency', 'percent'] as const\nexport type ValueFormat = (typeof VALUE_FORMATS)[number]\n\nexport type MeasureMeta = FieldMeta & {\n /** `percent` espera a fração (0.25 = 25%). */\n format?: ValueFormat\n /** Casas decimais; sem isso, vale o padrão do componente. */\n decimals?: number\n}\n\nexport type DimensionDef = FieldMeta & {\n sql: string\n type: 'number' | 'string'\n}\n\n/**\n * Agregações sobre uma expressão SQL (`tabela.coluna`). Todas valem nos dois formatos; no estrela,\n * agregam no grão da tabela de origem, como a soma.\n */\nexport const AGGREGATIONS = ['sum', 'count', 'countDistinct', 'avg', 'min', 'max'] as const\nexport type Aggregation = (typeof AGGREGATIONS)[number]\n\nexport const AGGREGATION_LABEL: Record<Aggregation, string> = {\n sum: 'Soma',\n count: 'Contagem',\n countDistinct: 'Contagem distinta',\n avg: 'Média',\n min: 'Mínimo',\n max: 'Máximo'\n}\n\n/** `COUNT` conta os valores não nulos; para contar linhas, use uma coluna que nunca é nula. */\nexport type AggregateMeasure = MeasureMeta & { type: Aggregation; sql: string }\nexport type SumMeasure = AggregateMeasure & { type: 'sum' }\nexport type RatioMeasure = MeasureMeta & { type: 'ratio'; numerator: string; denominator: string }\n/**\n * Combinação de outras medidas, já agregadas: `{nome}` vira o valor da medida (como `[Medida]` no\n * DAX). É o jeito de juntar medidas de fatos diferentes: `COALESCE({custoMmo}, 0) + COALESCE({custoAppa}, 0)`.\n */\nexport type FormulaMeasure = MeasureMeta & { type: 'formula'; expression: string }\nexport type MeasureDef = AggregateMeasure | RatioMeasure | FormulaMeasure\n\nexport const isAggregate = (measure: { type: unknown }): measure is AggregateMeasure =>\n AGGREGATIONS.includes(measure.type as Aggregation)\n\n/** O SQL agregado da medida: `SUM(x)`, `COUNT(DISTINCT x)`, `AVG(x)`... */\nexport function aggregateSql(measure: Pick<AggregateMeasure, 'type' | 'sql'>): string {\n if (measure.type === 'countDistinct') return `COUNT(DISTINCT ${measure.sql})`\n const fn = { sum: 'SUM', count: 'COUNT', avg: 'AVG', min: 'MIN', max: 'MAX' }[measure.type]\n return `${fn}(${measure.sql})`\n}\n\n/**\n * Tabela do modelo: o SQL é a \"consulta de origem\" (tabela, view ou SELECT). Não há tipo fato ou\n * dimensão: o papel sai dos relacionamentos (quem está no lado 1 filtra quem está no lado N) e de\n * onde ficam as medidas.\n */\nexport type TableDef = FieldMeta & {\n sql: string\n /** Coluna única da tabela; o diagrama usa ela para decidir o lado 1 ao criar relacionamentos. */\n key?: string\n}\n\n/** Relacionamento N:1 — o filtro do lado 1 (`to`) chega às linhas do lado N (`from`). */\nexport type RelationshipDef = {\n /** `tabela.coluna` do lado N (os lançamentos). */\n from: string\n /** `tabela.coluna` do lado 1 (a chave do cadastro). */\n to: string\n /**\n * \"Pressupor integridade referencial\" do Power BI: INNER JOIN em vez de LEFT JOIN, e a tabela do\n * lado 1 entra sempre na consulta da fato. Linhas do lado N sem par no lado 1 ficam de fora (em\n * vez de virar um grupo \"em branco\") — é o jeito de um filtro fixo da dimensão valer para a fato.\n */\n inner?: boolean\n}\n\n/**\n * Modelo estrela: `tables` (fato/dimensão) + `relationships`. Dimensões e medidas citam as colunas\n * como `tabela.coluna`; cada medida de agregação agrega no grão da própria tabela e chega às\n * dimensões seguindo os relacionamentos.\n */\nexport type ModelDef = {\n name: string\n tables: Record<string, TableDef>\n relationships?: RelationshipDef[]\n dimensions: Record<string, DimensionDef>\n measures: Record<string, MeasureDef>\n /**\n * Posição de cada card no diagrama do devtools (tabela -> canto superior esquerdo) e, se foi\n * redimensionado, o tamanho. Só visual: não muda nenhuma consulta. Card sem posição entra no\n * layout automático; sem tamanho, tem a largura padrão e a altura do conteúdo.\n */\n diagram?: Record<string, DiagramPosition>\n}\n\nexport type DiagramPosition = { x: number; y: number; width?: number; height?: number }\n\n/** Nomes citados como `{nome}` numa fórmula. */\nexport const formulaRefs = (expression: string) => [\n ...new Set([...expression.matchAll(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g)].map((match) => match[1]))\n]\n\n/** Medidas que uma medida usa diretamente (razão e fórmula). */\nexport function measureDeps(measure: MeasureDef): string[] {\n if (measure.type === 'ratio') return [measure.numerator, measure.denominator]\n if (measure.type === 'formula') return formulaRefs(measure.expression)\n return []\n}\n\nconst IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isText = (value: unknown): value is string => typeof value === 'string' && !!value.trim()\n\nfunction metaErrors(context: string, field: Record<string, unknown>): string[] {\n const errors: string[] = []\n for (const key of ['label', 'description'] as const) {\n if (field[key] !== undefined && typeof field[key] !== 'string')\n errors.push(`${context}: \\`${key}\\` precisa ser texto`)\n }\n if (field.format !== undefined && !VALUE_FORMATS.includes(field.format as ValueFormat))\n errors.push(`${context}: formato inválido \"${field.format}\"`)\n const { decimals } = field\n if (\n decimals !== undefined &&\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\n )\n errors.push(`${context}: \\`decimals\\` precisa ser um inteiro entre 0 e 10`)\n return errors\n}\n\n/** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */\nexport type ModelMeta = {\n name: string\n /** `table`: tabela do modelo de onde a dimensão sai (o nome, sem o SQL). */\n dimensions: Record<string, FieldMeta & { type: DimensionDef['type']; table?: string }>\n measures: Record<\n string,\n MeasureMeta & {\n type: MeasureDef['type']\n numerator?: string\n denominator?: string\n /** Medidas que a fórmula combina (a expressão em si fica no servidor). */\n refs?: string[]\n }\n >\n}\n\nexport function modelMeta(model: ModelDef): ModelMeta {\n const tableNames = Object.keys(model.tables)\n const pick = <T extends FieldMeta & MeasureMeta>(field: T) => ({\n ...(field.label !== undefined && { label: field.label }),\n ...(field.description !== undefined && { description: field.description }),\n ...(field.format !== undefined && { format: field.format }),\n ...(field.decimals !== undefined && { decimals: field.decimals })\n })\n return {\n name: model.name,\n dimensions: Object.fromEntries(\n Object.entries(model.dimensions).map(([name, dim]) => [\n name,\n { type: dim.type, table: referencedTables(dim.sql, tableNames)[0], ...pick(dim) }\n ])\n ),\n measures: Object.fromEntries(\n Object.entries(model.measures).map(([name, measure]) => [\n name,\n {\n type: measure.type,\n // Contagem sem formato: inteiro (sem casas decimais)\n ...((measure.type === 'count' || measure.type === 'countDistinct') && {\n format: 'integer' as const\n }),\n ...pick(measure),\n ...(measure.type === 'ratio' && {\n numerator: measure.numerator,\n denominator: measure.denominator\n }),\n ...(measure.type === 'formula' && { refs: formulaRefs(measure.expression) })\n }\n ])\n )\n }\n}\n\n/**\n * Lista os problemas do modelo (vazio = válido). Confere também a forma do objeto, porque o rascunho\n * do devtools chega como JSON da request.\n */\nexport function validateModel(model: ModelDef): string[] {\n if (!isRecord(model)) return ['modelo inválido']\n if (!isRecord(model.dimensions) || !isRecord(model.measures))\n return ['`dimensions` e `measures` são obrigatórios']\n // Rascunho/arquivo antigo: o formato base + joins foi removido\n if ('base' in model || 'joins' in model)\n return ['o formato `base` + `joins` não existe mais: use `tables` + `relationships`']\n if (!isRecord(model.tables)) return ['`tables` é obrigatório']\n if (model.relationships !== undefined && !Array.isArray(model.relationships))\n return ['`relationships` precisa ser uma lista']\n\n if (model.diagram !== undefined && !diagramValid(model.diagram))\n return ['`diagram` precisa ser { nome: { x, y, width?, height? } } com números (tamanho > 0)']\n\n const errors: string[] = []\n const { tables } = model\n const names = [...Object.keys(model.dimensions), ...Object.keys(model.measures)]\n\n for (const name of [...names, ...Object.keys(tables)]) {\n if (!IDENTIFIER.test(name)) errors.push(`nome inválido: \"${name}\"`)\n else if (name.startsWith('__')) errors.push(`nome reservado: \"${name}\" (começa com \"__\")`)\n }\n\n const duplicated = names.find((name, index) => names.indexOf(name) !== index)\n if (duplicated) errors.push(`nome repetido: \"${duplicated}\"`)\n\n for (const [name, table] of Object.entries(tables)) {\n if (!isRecord(table) || !isText(table.sql)) {\n errors.push(`tabela \"${name}\" sem SQL`)\n continue\n }\n if (table.key !== undefined && (typeof table.key !== 'string' || !IDENTIFIER.test(table.key)))\n errors.push(`tabela \"${name}\": key inválida`)\n errors.push(...metaErrors(`tabela \"${name}\"`, table))\n }\n\n const tableNames = Object.keys(tables)\n errors.push(...relationshipErrors(model, tableNames))\n\n for (const [name, dimension] of Object.entries(model.dimensions)) {\n if (!isRecord(dimension) || !isText(dimension.sql)) errors.push(`dimensão \"${name}\" sem SQL`)\n else if (dimension.type !== 'number' && dimension.type !== 'string')\n errors.push(`dimensão \"${name}\" com tipo inválido`)\n else {\n errors.push(...metaErrors(`dimensão \"${name}\"`, dimension))\n if (referencedTables(dimension.sql, tableNames).length !== 1)\n errors.push(`dimensão \"${name}\" precisa usar colunas de uma tabela só (\\`tabela.coluna\\`)`)\n }\n }\n\n const edges = modelEdges(model)\n for (const [name, measure] of Object.entries(model.measures)) {\n if (!isRecord(measure)) {\n errors.push(`medida \"${name}\" inválida`)\n } else if (isAggregate(measure)) {\n if (!isText(measure.sql)) errors.push(`medida \"${name}\" sem SQL`)\n else {\n const used = referencedTables(measure.sql, tableNames)\n if (!used.length)\n errors.push(`medida \"${name}\" precisa usar colunas de uma tabela (\\`tabela.coluna\\`)`)\n else if (!measureHome(used, edges))\n errors.push(\n `medida \"${name}\" usa ${used.join(', ')}, mas nenhuma delas chega às outras pelos relacionamentos`\n )\n }\n } else if (measure.type === 'ratio' || measure.type === 'formula') {\n if (measure.type === 'formula' && !isText(measure.expression))\n errors.push(`medida \"${name}\" sem expressão`)\n else if (measure.type === 'formula' && !formulaRefs(measure.expression).length)\n errors.push(`medida \"${name}\": a fórmula precisa citar medidas como {nome}`)\n for (const ref of measureDeps(measure)) {\n if (typeof ref !== 'string' || !model.measures[ref])\n errors.push(`medida \"${name}\" referencia \"${ref}\", que não existe`)\n }\n } else {\n errors.push(`medida \"${name}\" com tipo inválido`)\n }\n if (isRecord(measure)) errors.push(...metaErrors(`medida \"${name}\"`, measure))\n }\n if (errors.length) return errors\n\n // Medidas circulares (a -> b -> a) só estourariam na hora de compilar a consulta\n const visit = (name: string, stack: string[]): string | null => {\n if (stack.includes(name)) return [...stack, name].join(' -> ')\n for (const dep of measureDeps(model.measures[name])) {\n const cycle = visit(dep, [...stack, name])\n if (cycle) return cycle\n }\n return null\n }\n for (const name of Object.keys(model.measures)) {\n const cycle = visit(name, [])\n if (cycle) {\n errors.push(`medida circular: ${cycle}`)\n break\n }\n }\n\n return errors\n}\n\nconst diagramValid = (diagram: unknown) =>\n isRecord(diagram) &&\n Object.values(diagram).every(\n (point) =>\n isRecord(point) &&\n Number.isFinite(point.x) &&\n Number.isFinite(point.y) &&\n [point.width, point.height].every(\n (size) => size === undefined || (Number.isFinite(size) && (size as number) > 0)\n )\n )\n\nfunction relationshipErrors(model: ModelDef, tableNames: string[]): string[] {\n const errors: string[] = []\n const seen = new Set<string>()\n for (const [index, relationship] of (model.relationships ?? []).entries()) {\n const context = `relacionamento ${index + 1}`\n if (!isRecord(relationship)) {\n errors.push(`${context} inválido`)\n continue\n }\n const from = parseColumnRef(relationship.from)\n const to = parseColumnRef(relationship.to)\n if (!from || !to) {\n errors.push(`${context}: use \\`tabela.coluna\\` em \\`from\\` e \\`to\\``)\n continue\n }\n for (const ref of [from, to])\n if (!tableNames.includes(ref.table))\n errors.push(`${context}: tabela \"${ref.table}\" não existe`)\n if (from.table === to.table)\n errors.push(`${context}: liga a tabela \"${from.table}\" a ela mesma`)\n if (relationship.inner !== undefined && typeof relationship.inner !== 'boolean')\n errors.push(`${context}: \\`inner\\` precisa ser true/false`)\n const pair = `${from.table}->${to.table}`\n if (seen.has(pair))\n errors.push(`${context}: já existe um relacionamento de \"${from.table}\" para \"${to.table}\"`)\n seen.add(pair)\n }\n if (errors.length) return errors\n\n // Dois caminhos até a mesma tabela deixariam o filtro ambíguo (o Power BI deixa um inativo)\n const edges = modelEdges(model)\n for (const table of tableNames) {\n const { ambiguous, paths } = reachFrom(table, edges)\n if (ambiguous.length) {\n errors.push(\n `caminho ambíguo de \"${table}\" até \"${ambiguous[0]}\": há mais de um relacionamento levando lá`\n )\n break\n }\n // Ciclo: a tabela alcança a si mesma\n if (edges.some((edge) => edge.to.table === table && paths.has(edge.from.table))) {\n errors.push(`relacionamentos em ciclo passando por \"${table}\"`)\n break\n }\n }\n return errors\n}\n","import {\n type DiagramPosition,\n type FieldMeta,\n isAggregate,\n type MeasureDef,\n type MeasureMeta,\n type ModelDef,\n type TableDef\n} from './model'\n\n/**\n * Gera o código TS de um modelo (`defineModel({...})`). Usado pelo \"Copiar TS\" do devtools e pelo\n * \"Salvar no arquivo\" do servidor, que troca só a chamada do `defineModel` dentro do arquivo.\n */\n\nconst diagramCode = ({ x, y, width, height }: DiagramPosition) =>\n `{ x: ${x}, y: ${y}${width === undefined ? '' : `, width: ${width}`}${height === undefined ? '' : `, height: ${height}`} }`\n\n/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_, item) =>\n item && typeof item === 'object' && !Array.isArray(item)\n ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n : item\n )\n}\n\nexport function literal(text: string) {\n if (text.includes('\\n'))\n return `\\`${text.replaceAll('\\\\', '\\\\\\\\').replaceAll('`', '\\\\`').replaceAll('${', '\\\\${')}\\``\n return `'${text.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\")}'`\n}\n\nfunction metaEntries(field: FieldMeta & MeasureMeta) {\n const entries: string[] = []\n if (field.label !== undefined) entries.push(`label: ${literal(field.label)}`)\n if (field.description !== undefined) entries.push(`description: ${literal(field.description)}`)\n if (field.format !== undefined) entries.push(`format: '${field.format}'`)\n if (field.decimals !== undefined) entries.push(`decimals: ${field.decimals}`)\n return entries\n}\n\nfunction measureCode(measure: MeasureDef) {\n const meta = metaEntries(measure)\n const args = isAggregate(measure)\n ? [literal(measure.sql)]\n : measure.type === 'formula'\n ? [literal(measure.expression)]\n : [literal(measure.numerator), literal(measure.denominator)]\n if (meta.length) args.push(`{ ${meta.join(', ')} }`)\n return `${measure.type}(${args.join(', ')})`\n}\n\nfunction tableCode(table: TableDef) {\n const fields = [\n ...(table.key !== undefined ? [`key: ${literal(table.key)}`] : []),\n `sql: ${literal(table.sql)}`,\n ...metaEntries(table)\n ]\n return `{ ${fields.join(', ')} }`\n}\n\nconst entries = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.entries(record)\n .map(([name, value]) => ` ${name}: ${render(value)}`)\n .join(',\\n')\n\n/** `{ ... }` com uma entrada por linha, ou `{}` quando vazio (modelo recém-criado). */\nconst block = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.keys(record).length ? `{\\n${entries(record, render)}\\n }` : '{}'\n\n/** O objeto passado ao `defineModel`, começando em `{` (sem indentação na primeira linha). */\nexport function modelObjectCode(model: ModelDef): string {\n const lines = ['{', ` name: ${literal(model.name)},`]\n lines.push(` tables: ${block(model.tables, tableCode)},`)\n if (model.relationships?.length)\n lines.push(\n ` relationships: [\\n${model.relationships\n .map(\n (relationship) =>\n ` { from: ${literal(relationship.from)}, to: ${literal(relationship.to)}${relationship.inner ? ', inner: true' : ''} }`\n )\n .join(',\\n')}\\n ],`\n )\n lines.push(\n ` dimensions: ${block(model.dimensions, (dim) =>\n [`{ sql: ${literal(dim.sql)}`, `type: '${dim.type}'`, ...metaEntries(dim)]\n .join(', ')\n .concat(' }')\n )},`,\n ` measures: ${block(model.measures, measureCode)}${model.diagram ? ',' : ''}`\n )\n if (model.diagram) lines.push(` diagram: {\\n${entries(model.diagram, diagramCode)}\\n }`)\n lines.push('}')\n return lines.join('\\n')\n}\n\n/** Helpers do `@bluemarble/bi/server` que o código do modelo usa. */\nexport function modelImports(model: ModelDef) {\n // O helper tem o nome do tipo (`sum(...)`, `countDistinct(...)`, `formula(...)`)\n const types = new Set(Object.values(model.measures).map((measure) => measure.type))\n return ['defineModel', ...types].sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }))\n}\n\n/** Arquivo completo, pronto para colar em `src/dashboard/models/<nome>.ts`. */\nexport function toModelCode(model: ModelDef): string {\n return [\n `import { ${modelImports(model).join(', ')} } from '@bluemarble/bi/server'`,\n '',\n `export const ${model.name} = defineModel(${modelObjectCode(model)})`,\n ''\n ].join('\\n')\n}\n"],"mappings":"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"}
@@ -101,8 +101,10 @@ type DiagramPosition = {
101
101
  /** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */
102
102
  type ModelMeta = {
103
103
  name: string;
104
+ /** `table`: tabela do modelo de onde a dimensão sai (o nome, sem o SQL). */
104
105
  dimensions: Record<string, FieldMeta & {
105
106
  type: DimensionDef['type'];
107
+ table?: string;
106
108
  }>;
107
109
  measures: Record<string, MeasureMeta & {
108
110
  type: MeasureDef['type'];
@@ -119,209 +121,6 @@ declare function modelMeta(model: ModelDef): ModelMeta;
119
121
  */
120
122
  declare function validateModel(model: ModelDef): string[];
121
123
  //#endregion
122
- //#region src/types.d.ts
123
- type FilterValue = string | number;
124
- /** Filtros por dimensão; lista vazia (ou ausente) = sem filtro ("Todos"). */
125
- type DashboardFilters = Record<string, FilterValue[]>;
126
- /**
127
- * Pedidos do devtools. O servidor só aceita quando o `runQuery` recebe `devtools` (em dev); fora
128
- * disso `debug` é ignorado e o resto é recusado com 400.
129
- */
130
- type DashboardDevtoolsRequest = {
131
- /** Devolve SQL compilado, parâmetros e tempos em `result.devtools.debug`. */
132
- debug?: boolean;
133
- /** Rascunho do modelo editado no devtools: substitui o modelo registrado só nesta consulta. */
134
- model?: ModelDef;
135
- /** Devolve a definição do modelo registrado e o que o servidor permite (`capabilities`). */
136
- describe?: boolean;
137
- /** Esvazia o cache de resultados do processo antes de executar. */
138
- clearCache?: boolean;
139
- /**
140
- * Só compila: devolve o SQL e os parâmetros em `result.devtools.debug`, sem executar (o devtools
141
- * usa para mostrar o SQL de uma consulta que deu erro).
142
- */
143
- compile?: boolean;
144
- /** Devolve o `EXPLAIN` da consulta em vez das linhas. */
145
- explain?: boolean;
146
- /** Mede a agregação de cada fato usada pela consulta, isoladamente. */
147
- profile?: boolean;
148
- /** Tabelas e colunas do banco (`information_schema`), para o autocomplete do editor. */
149
- schema?: boolean;
150
- /** Colunas de cada tabela (uma linha de cada), para o diagrama do modelo. */
151
- columns?: boolean;
152
- /** Executa cada pedaço do modelo (`devtools.model` ou o registrado) contra o banco. */
153
- validate?: boolean;
154
- /** Grava o rascunho no arquivo do modelo (`preview` só devolve o conteúdo novo). */
155
- save?: 'preview' | 'write';
156
- /** Grava o layout de gráficos em `<layoutsDir>/<id>.json` (`preview` só devolve o conteúdo novo). */
157
- saveLayout?: {
158
- layout: ChartsLayout;
159
- mode: 'preview' | 'write';
160
- };
161
- /** Grava o resultado da consulta (modelo registrado) como snapshot de regressão. */
162
- snapshot?: {
163
- name: string;
164
- overwrite?: boolean;
165
- };
166
- /** Lista os snapshots salvos deste modelo. */
167
- snapshots?: boolean;
168
- };
169
- type DashboardQuery = {
170
- model: string;
171
- /** Dimensões agrupadas (viram GROUP BY). */
172
- dimensions?: string[];
173
- /** Medidas calculadas para cada grupo. */
174
- measures?: string[];
175
- filters?: DashboardFilters;
176
- /** Devolve só os metadados do modelo (rótulos, descrições, formatos; sem SQL) em `result.meta`. */
177
- meta?: boolean;
178
- /**
179
- * Várias consultas do mesmo modelo num request (o client manda assim as consultas de um render).
180
- * O servidor junta numa SQL só as que têm as mesmas dimensões e filtros e vêm das mesmas fatos, e
181
- * devolve uma resposta por consulta em `result.results`, na mesma ordem. Do `devtools`, só
182
- * `debug` e `model` (rascunho) valem aqui.
183
- */
184
- queries?: DashboardBatchQuery[];
185
- devtools?: DashboardDevtoolsRequest;
186
- };
187
- /** Uma consulta de um lote (`DashboardQuery.queries`): o modelo e o devtools são os do lote. */
188
- type DashboardBatchQuery = Pick<DashboardQuery, 'dimensions' | 'measures' | 'filters'>;
189
- /** Resposta de uma consulta do lote: as linhas ou o erro (de consulta) dela. */
190
- type DashboardBatchResult = {
191
- rows: DashboardRow[];
192
- error?: string;
193
- /** Quantas consultas do lote saíram da mesma SQL (contando esta); o `debug` é o dessa SQL. */
194
- batched?: number;
195
- devtools?: {
196
- debug?: DashboardQueryDebug;
197
- };
198
- };
199
- type DashboardRow = Record<string, string | number | null>;
200
- type DashboardQueryDebug = {
201
- sql: string;
202
- params: (string | number)[];
203
- /** Tempo da execução no banco (num cache hit, o da execução original). */
204
- durationMs: number;
205
- cached: boolean;
206
- /** A consulta rodou contra o rascunho do devtools, não contra o modelo registrado. */
207
- draft: boolean;
208
- };
209
- type ProfileStep = {
210
- /** `fato:<tabela>` ou `consulta` (a consulta inteira). */
211
- name: string;
212
- durationMs: number;
213
- /** Linhas da agregação da fato (grupos) com os filtros aplicados. */
214
- rows: number | null;
215
- };
216
- type SchemaTable = {
217
- name: string;
218
- type: 'table' | 'view';
219
- columns: {
220
- name: string;
221
- type: string;
222
- }[];
223
- };
224
- /** Colunas devolvidas por cada pedaço do modelo (vazio quando não veio nenhuma linha). */
225
- type ModelColumns = {
226
- /** Colunas de cada tabela. */
227
- tables: Record<string, string[]>;
228
- /** `tabela.coluna` com valor numérico na linha lida. */
229
- numeric: string[];
230
- };
231
- type ModelValidation = {
232
- /**
233
- * Erro do banco por pedaço: `table:<nome>`, `relationship:<índice>`, `dimension:<nome>`,
234
- * `measure:<nome>`.
235
- */
236
- errors: Record<string, string>;
237
- warnings: Record<string, string>;
238
- columns: ModelColumns;
239
- };
240
- type SaveModelResult = {
241
- path: string;
242
- previous: string;
243
- content: string;
244
- written: boolean;
245
- warnings: string[];
246
- };
247
- type DashboardSnapshot = {
248
- name: string;
249
- createdAt: string;
250
- query: Omit<DashboardQuery, 'devtools' | 'meta'>;
251
- rows: DashboardRow[];
252
- };
253
- type DevtoolsCapabilities = {
254
- /** O endpoint sabe onde ficam os arquivos dos modelos (`modelsDir`). */
255
- saveModel: boolean;
256
- /** O endpoint sabe onde gravar snapshots (`snapshotsDir`). */
257
- snapshots: boolean;
258
- /** O endpoint sabe onde ficam os arquivos de layout de gráficos (`layoutsDir`). */
259
- saveLayout: boolean;
260
- /** Tipos de gráfico do app que o endpoint conhece (`chartTypes`): só esses ele aceita salvar. */
261
- chartTypes: string[];
262
- };
263
- type DashboardResult = {
264
- rows: DashboardRow[];
265
- /** Respostas de um lote (`query.queries`), na ordem das consultas. */
266
- results?: DashboardBatchResult[];
267
- /**
268
- * Quantas consultas saíram da mesma SQL que esta (contando ela), quando foi num lote. O
269
- * `devtools.debug` é o dessa SQL.
270
- */
271
- batched?: number;
272
- meta?: ModelMeta;
273
- devtools?: {
274
- model?: ModelDef;
275
- capabilities?: DevtoolsCapabilities;
276
- debug?: DashboardQueryDebug;
277
- explain?: DashboardRow[];
278
- profile?: ProfileStep[];
279
- schema?: SchemaTable[];
280
- validation?: ModelValidation;
281
- columns?: ModelColumns;
282
- /** Pedaços que o banco recusou ao ler as colunas (mesmas chaves da validação). */
283
- columnErrors?: Record<string, string>;
284
- save?: SaveModelResult;
285
- snapshot?: {
286
- path: string;
287
- };
288
- snapshots?: DashboardSnapshot[];
289
- };
290
- };
291
- //#endregion
292
- //#region src/formatRules.d.ts
293
- declare const COLUMN_ALIGNS: readonly ['left', 'center', 'right'];
294
- type ColumnAlign = (typeof COLUMN_ALIGNS)[number];
295
- declare const COLUMN_VERTICAL_ALIGNS: readonly ['top', 'middle', 'bottom'];
296
- type ColumnVerticalAlign = (typeof COLUMN_VERTICAL_ALIGNS)[number];
297
- declare const RULE_OPERATORS: readonly ['>', '>=', '<', '<=', '=', '!=', 'between', 'contains', 'empty', 'notEmpty'];
298
- type RuleOperator = (typeof RULE_OPERATORS)[number];
299
- type FormatRule = {
300
- /**
301
- * Dimensão ou medida avaliada. Pode estar fora da tabela: entra na consulta sem coluna (uma
302
- * dimensão de fora divide as linhas se tiver mais de um valor por linha).
303
- */
304
- field: string;
305
- operator: RuleOperator;
306
- /** Valor comparado com o valor bruto (percentual como fração: 10% = 0.1). */
307
- value?: number | string;
308
- /** Fim do intervalo do `between` (inclusivo, como o `value`). */
309
- to?: number;
310
- /** `cell` (padrão) pinta uma célula; `row`, a linha inteira. */
311
- target?: 'cell' | 'row';
312
- /** Coluna pintada quando `target` é `cell`. Padrão: o próprio `field`. */
313
- column?: string;
314
- /** Cor do texto. */
315
- color?: string;
316
- /** Cor de fundo. */
317
- background?: string;
318
- bold?: boolean;
319
- /** Alinhamento horizontal da célula (ou das células da linha). */
320
- align?: ColumnAlign;
321
- /** Alinhamento vertical da célula (ou das células da linha). */
322
- verticalAlign?: ColumnVerticalAlign;
323
- };
324
- //#endregion
325
124
  //#region src/layout.d.ts
326
125
  declare const CHART_TYPES: readonly ['kpi', 'combo', 'bar', 'donut', 'table'];
327
126
  type ChartType = (typeof CHART_TYPES)[number];
@@ -396,8 +195,32 @@ type BarChartDef = BaseChartDef & ColumnsDisplay & {
396
195
  formatDimension?: string;
397
196
  clickToFilter?: boolean;
398
197
  hideEmpty?: boolean;
198
+ /** Todos os valores da dimensão no eixo, com ou sem dado (sobrepõe `hideEmpty`). */
199
+ showEmpty?: boolean;
399
200
  showValues?: boolean;
201
+ /** Onde fica o valor de cada barra. Padrão: `outside` (na ponta, fora da barra). */
202
+ valuePosition?: BarValuePosition;
203
+ /** Cor do valor. Padrão: escuro fora da barra, branco dentro. */
204
+ valueColor?: string;
205
+ /** Tamanho do valor, em px. Padrão: 12. */
206
+ valueSize?: number;
207
+ /** Empilhado: total da pilha na ponta. */
208
+ showTotal?: boolean;
209
+ /** Só mostra o valor (e o total) que atende à regra, ex.: `{ operator: '!=', value: 0 }`. */
210
+ valueRule?: ValueRule;
211
+ /**
212
+ * Com `clickToFilter`, o filtro da própria dimensão (e da série) esconde o resto em vez de só
213
+ * apagá-lo. Padrão: `false` (apaga).
214
+ */
215
+ hideUnselected?: boolean;
216
+ /**
217
+ * Cor por série: `{ medida: '#rrggbb' }` ou, com `series`, `{ valor: '#rrggbb' }` (também
218
+ * "(em branco)" e "Outros"). O que faltar segue a paleta.
219
+ */
220
+ colors?: Record<string, string>;
400
221
  };
222
+ declare const BAR_VALUE_POSITIONS: readonly ['outside', 'insideEnd', 'center', 'insideBase'];
223
+ type BarValuePosition = (typeof BAR_VALUE_POSITIONS)[number];
401
224
  type DonutChartDef = BaseChartDef & ColumnsDisplay & {
402
225
  type: 'donut';
403
226
  dimension: string;
@@ -408,7 +231,19 @@ type DonutChartDef = BaseChartDef & ColumnsDisplay & {
408
231
  percentDecimals?: number;
409
232
  formatDimension?: string;
410
233
  clickToFilter?: boolean;
234
+ /** Lado da legenda. Padrão: `right` (com nome, % e valor; em cima/embaixo, só o nome). */
235
+ legendPosition?: DonutLegendPosition;
236
+ /** Valor de cada fatia do lado de fora da rosca. */
237
+ showValues?: boolean;
238
+ /** O que o valor de fora mostra. Padrão: `both` (`44.981 (51%)`). */
239
+ valueContent?: DonutValueContent;
240
+ /** Total no furo da rosca. Padrão: `true`. */
241
+ showTotal?: boolean;
411
242
  };
243
+ declare const DONUT_LEGEND_POSITIONS: readonly ['right', 'left', 'top', 'bottom'];
244
+ type DonutLegendPosition = (typeof DONUT_LEGEND_POSITIONS)[number];
245
+ declare const DONUT_VALUE_CONTENTS: readonly ['both', 'value', 'percent'];
246
+ type DonutValueContent = (typeof DONUT_VALUE_CONTENTS)[number];
412
247
  type TableChartDef = BaseChartDef & ColumnsDisplay & {
413
248
  type: 'table';
414
249
  dimensions?: string[];
@@ -505,7 +340,11 @@ type CardStyle = {
505
340
  };
506
341
  /** Cards de todos os gráficos do layout: estilo padrão e espaço entre eles. */
507
342
  type CardsStyle = CardStyle & {
508
- /** Espaço entre os cards, em px. */
343
+ /** Espaço entre as linhas de cards, em px. */
344
+ rowGap?: number;
345
+ /** Espaço entre as colunas de cards, em px. */
346
+ columnGap?: number;
347
+ /** Espaço entre linhas e colunas, em px (formato antigo; `rowGap`/`columnGap` têm prioridade). */
509
348
  gap?: number;
510
349
  };
511
350
  type ChartsLayout = {
@@ -523,7 +362,7 @@ type FieldSpec = {
523
362
  label: string;
524
363
  hint?: string;
525
364
  /** Agrupamento no formulário do devtools. */
526
- section: 'Gráfico' | 'Dados' | 'Aparência' | 'Rótulos e formatos' | 'Formatação condicional' | 'Filtro' | 'Tela' | 'Card';
365
+ section: 'Gráfico' | 'Dados' | 'Aparência' | 'Valores' | 'Comportamento' | 'Rótulos e formatos' | 'Formatação condicional' | 'Filtro' | 'Tela' | 'Card';
527
366
  required?: boolean;
528
367
  /** Valor que o componente usa quando o campo não está no JSON. */
529
368
  default?: unknown;
@@ -602,6 +441,18 @@ type FieldSpec = {
602
441
  /** Regras de formatação condicional (`FormatRule[]`). */
603
442
  {
604
443
  kind: 'formatRules';
444
+ } |
445
+ /**
446
+ * `{ série: '#rrggbb' }`: uma cor por medida do gráfico ou, quando o campo `dimension` em
447
+ * `values` está preenchido, por valor dessa dimensão (o devtools não lista os valores; digita).
448
+ */
449
+ {
450
+ kind: 'colors';
451
+ values?: string;
452
+ } |
453
+ /** `ValueRule` (`{ operator, value, to? }`): ausente = sem regra (o devtools mostra uma checkbox). */
454
+ {
455
+ kind: 'valueRule';
605
456
  });
606
457
  /**
607
458
  * Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e
@@ -646,5 +497,219 @@ declare function validateLayout(layout: ChartsLayout, types?: ChartTypes): strin
646
497
  */
647
498
  declare function layoutModelWarnings(layout: ChartsLayout, model: Pick<ModelDef, 'dimensions' | 'measures'>, types?: ChartTypes): string[];
648
499
  //#endregion
649
- export { TableDef as $, DashboardQueryDebug as A, SchemaTable as B, ColumnVerticalAlign as C, DashboardDevtoolsRequest as D, DashboardBatchResult as E, FilterValue as F, FormulaMeasure as G, Aggregation as H, ModelColumns as I, ModelDef as J, MeasureDef as K, ModelValidation as L, DashboardRow as M, DashboardSnapshot as N, DashboardFilters as O, DevtoolsCapabilities as P, SumMeasure as Q, ProfileStep as R, ColumnAlign as S, DashboardBatchQuery as T, DimensionDef as U, AggregateMeasure as V, FieldMeta as W, RatioMeasure as X, ModelMeta as Y, RelationshipDef as Z, KpiChartDef as _, CardsStyle as a, resolveChartTypes as b, ChartType as c, ChartsLayout as d, ValueFormat as et, ComboChartDef as f, FilterDef as g, FieldSpec as h, CardStyle as i, DashboardResult as j, DashboardQuery as k, ChartTypeSpec as l, DonutChartDef as m, BuiltinChartDef as n, validateModel as nt, ChartDef as o, CustomChartDef as p, MeasureMeta as q, CanvasDef as r, ChartNumberFormat as s, BarChartDef as t, modelMeta as tt, ChartTypes as u, TableChartDef as v, FormatRule as w, validateLayout as x, layoutModelWarnings as y, SaveModelResult as z };
650
- //# sourceMappingURL=layout-CoGyGWYM.d.ts.map
500
+ //#region src/types.d.ts
501
+ type FilterValue = string | number;
502
+ /** Filtros por dimensão; lista vazia (ou ausente) = sem filtro ("Todos"). */
503
+ type DashboardFilters = Record<string, FilterValue[]>;
504
+ /**
505
+ * Pedidos do devtools. O servidor só aceita quando o `runQuery` recebe `devtools` (em dev); fora
506
+ * disso `debug` é ignorado e o resto é recusado com 400.
507
+ */
508
+ type DashboardDevtoolsRequest = {
509
+ /** Devolve SQL compilado, parâmetros e tempos em `result.devtools.debug`. */
510
+ debug?: boolean;
511
+ /** Rascunho do modelo editado no devtools: substitui o modelo registrado só nesta consulta. */
512
+ model?: ModelDef;
513
+ /** Devolve a definição do modelo registrado e o que o servidor permite (`capabilities`). */
514
+ describe?: boolean;
515
+ /** Esvazia o cache de resultados do processo antes de executar. */
516
+ clearCache?: boolean;
517
+ /**
518
+ * Só compila: devolve o SQL e os parâmetros em `result.devtools.debug`, sem executar (o devtools
519
+ * usa para mostrar o SQL de uma consulta que deu erro).
520
+ */
521
+ compile?: boolean;
522
+ /** Devolve o `EXPLAIN` da consulta em vez das linhas. */
523
+ explain?: boolean;
524
+ /** Mede a agregação de cada fato usada pela consulta, isoladamente. */
525
+ profile?: boolean;
526
+ /** Tabelas e colunas do banco (`information_schema`), para o autocomplete do editor. */
527
+ schema?: boolean;
528
+ /** Colunas de cada tabela (uma linha de cada), para o diagrama do modelo. */
529
+ columns?: boolean;
530
+ /** Executa cada pedaço do modelo (`devtools.model` ou o registrado) contra o banco. */
531
+ validate?: boolean;
532
+ /** Grava o rascunho no arquivo do modelo (`preview` só devolve o conteúdo novo). */
533
+ save?: 'preview' | 'write';
534
+ /** Grava o layout de gráficos em `<layoutsDir>/<id>.json` (`preview` só devolve o conteúdo novo). */
535
+ saveLayout?: {
536
+ layout: ChartsLayout;
537
+ mode: 'preview' | 'write';
538
+ };
539
+ /** Grava o resultado da consulta (modelo registrado) como snapshot de regressão. */
540
+ snapshot?: {
541
+ name: string;
542
+ overwrite?: boolean;
543
+ };
544
+ /** Lista os snapshots salvos deste modelo. */
545
+ snapshots?: boolean;
546
+ };
547
+ type DashboardQuery = {
548
+ model: string;
549
+ /** Dimensões agrupadas (viram GROUP BY). */
550
+ dimensions?: string[];
551
+ /** Medidas calculadas para cada grupo. */
552
+ measures?: string[];
553
+ filters?: DashboardFilters;
554
+ /** Devolve só os metadados do modelo (rótulos, descrições, formatos; sem SQL) em `result.meta`. */
555
+ meta?: boolean;
556
+ /**
557
+ * Várias consultas do mesmo modelo num request (o client manda assim as consultas de um render).
558
+ * O servidor junta numa SQL só as que têm as mesmas dimensões e filtros e vêm das mesmas fatos, e
559
+ * devolve uma resposta por consulta em `result.results`, na mesma ordem. Do `devtools`, só
560
+ * `debug` e `model` (rascunho) valem aqui.
561
+ */
562
+ queries?: DashboardBatchQuery[];
563
+ devtools?: DashboardDevtoolsRequest;
564
+ };
565
+ /** Uma consulta de um lote (`DashboardQuery.queries`): o modelo e o devtools são os do lote. */
566
+ type DashboardBatchQuery = Pick<DashboardQuery, 'dimensions' | 'measures' | 'filters'>;
567
+ /** Resposta de uma consulta do lote: as linhas ou o erro (de consulta) dela. */
568
+ type DashboardBatchResult = {
569
+ rows: DashboardRow[];
570
+ error?: string;
571
+ /** Quantas consultas do lote saíram da mesma SQL (contando esta); o `debug` é o dessa SQL. */
572
+ batched?: number;
573
+ devtools?: {
574
+ debug?: DashboardQueryDebug;
575
+ };
576
+ };
577
+ type DashboardRow = Record<string, string | number | null>;
578
+ type DashboardQueryDebug = {
579
+ sql: string;
580
+ params: (string | number)[];
581
+ /** Tempo da execução no banco (num cache hit, o da execução original). */
582
+ durationMs: number;
583
+ cached: boolean;
584
+ /** A consulta rodou contra o rascunho do devtools, não contra o modelo registrado. */
585
+ draft: boolean;
586
+ };
587
+ type ProfileStep = {
588
+ /** `fato:<tabela>` ou `consulta` (a consulta inteira). */
589
+ name: string;
590
+ durationMs: number;
591
+ /** Linhas da agregação da fato (grupos) com os filtros aplicados. */
592
+ rows: number | null;
593
+ };
594
+ type SchemaTable = {
595
+ name: string;
596
+ type: 'table' | 'view';
597
+ columns: {
598
+ name: string;
599
+ type: string;
600
+ }[];
601
+ };
602
+ /** Colunas devolvidas por cada pedaço do modelo (vazio quando não veio nenhuma linha). */
603
+ type ModelColumns = {
604
+ /** Colunas de cada tabela. */
605
+ tables: Record<string, string[]>;
606
+ /** `tabela.coluna` com valor numérico na linha lida. */
607
+ numeric: string[];
608
+ };
609
+ type ModelValidation = {
610
+ /**
611
+ * Erro do banco por pedaço: `table:<nome>`, `relationship:<índice>`, `dimension:<nome>`,
612
+ * `measure:<nome>`.
613
+ */
614
+ errors: Record<string, string>;
615
+ warnings: Record<string, string>;
616
+ columns: ModelColumns;
617
+ };
618
+ type SaveModelResult = {
619
+ path: string;
620
+ previous: string;
621
+ content: string;
622
+ written: boolean;
623
+ warnings: string[];
624
+ };
625
+ type DashboardSnapshot = {
626
+ name: string;
627
+ createdAt: string;
628
+ query: Omit<DashboardQuery, 'devtools' | 'meta'>;
629
+ rows: DashboardRow[];
630
+ };
631
+ type DevtoolsCapabilities = {
632
+ /** O endpoint sabe onde ficam os arquivos dos modelos (`modelsDir`). */
633
+ saveModel: boolean;
634
+ /** O endpoint sabe onde gravar snapshots (`snapshotsDir`). */
635
+ snapshots: boolean;
636
+ /** O endpoint sabe onde ficam os arquivos de layout de gráficos (`layoutsDir`). */
637
+ saveLayout: boolean;
638
+ /** Tipos de gráfico do app que o endpoint conhece (`chartTypes`): só esses ele aceita salvar. */
639
+ chartTypes: string[];
640
+ };
641
+ type DashboardResult = {
642
+ rows: DashboardRow[];
643
+ /** Respostas de um lote (`query.queries`), na ordem das consultas. */
644
+ results?: DashboardBatchResult[];
645
+ /**
646
+ * Quantas consultas saíram da mesma SQL que esta (contando ela), quando foi num lote. O
647
+ * `devtools.debug` é o dessa SQL.
648
+ */
649
+ batched?: number;
650
+ meta?: ModelMeta;
651
+ devtools?: {
652
+ model?: ModelDef;
653
+ capabilities?: DevtoolsCapabilities;
654
+ debug?: DashboardQueryDebug;
655
+ explain?: DashboardRow[];
656
+ profile?: ProfileStep[];
657
+ schema?: SchemaTable[];
658
+ validation?: ModelValidation;
659
+ columns?: ModelColumns;
660
+ /** Pedaços que o banco recusou ao ler as colunas (mesmas chaves da validação). */
661
+ columnErrors?: Record<string, string>;
662
+ save?: SaveModelResult;
663
+ snapshot?: {
664
+ path: string;
665
+ };
666
+ snapshots?: DashboardSnapshot[];
667
+ };
668
+ };
669
+ //#endregion
670
+ //#region src/formatRules.d.ts
671
+ declare const COLUMN_ALIGNS: readonly ['left', 'center', 'right'];
672
+ type ColumnAlign = (typeof COLUMN_ALIGNS)[number];
673
+ declare const COLUMN_VERTICAL_ALIGNS: readonly ['top', 'middle', 'bottom'];
674
+ type ColumnVerticalAlign = (typeof COLUMN_VERTICAL_ALIGNS)[number];
675
+ declare const RULE_OPERATORS: readonly ['>', '>=', '<', '<=', '=', '!=', 'between', 'contains', 'empty', 'notEmpty'];
676
+ type RuleOperator = (typeof RULE_OPERATORS)[number];
677
+ type FormatRule = {
678
+ /**
679
+ * Dimensão ou medida avaliada. Pode estar fora da tabela: entra na consulta sem coluna (uma
680
+ * dimensão de fora divide as linhas se tiver mais de um valor por linha).
681
+ */
682
+ field: string;
683
+ operator: RuleOperator;
684
+ /** Valor comparado com o valor bruto (percentual como fração: 10% = 0.1). */
685
+ value?: number | string;
686
+ /** Fim do intervalo do `between` (inclusivo, como o `value`). */
687
+ to?: number;
688
+ /** `cell` (padrão) pinta uma célula; `row`, a linha inteira. */
689
+ target?: 'cell' | 'row';
690
+ /** Coluna pintada quando `target` é `cell`. Padrão: o próprio `field`. */
691
+ column?: string;
692
+ /** Cor do texto. */
693
+ color?: string;
694
+ /** Cor de fundo. */
695
+ background?: string;
696
+ bold?: boolean;
697
+ /** Alinhamento horizontal da célula (ou das células da linha). */
698
+ align?: ColumnAlign;
699
+ /** Alinhamento vertical da célula (ou das células da linha). */
700
+ verticalAlign?: ColumnVerticalAlign;
701
+ };
702
+ /**
703
+ * Regra de um valor só (sem campo nem estilo), ex.: mostrar o rótulo da barra só quando o valor é
704
+ * diferente de zero: `{ "operator": "!=", "value": 0 }`.
705
+ */
706
+ type ValueRule = {
707
+ operator: ValueRuleOperator;
708
+ value: number;
709
+ to?: number;
710
+ };
711
+ declare const VALUE_RULE_OPERATORS: readonly ['>', '>=', '<', '<=', '=', '!=', 'between'];
712
+ type ValueRuleOperator = (typeof VALUE_RULE_OPERATORS)[number];
713
+ //#endregion
714
+ export { ModelMeta as $, ChartTypeSpec as A, KpiChartDef as B, BuiltinChartDef as C, ChartDef as D, CardsStyle as E, DonutChartDef as F, AggregateMeasure as G, layoutModelWarnings as H, DonutLegendPosition as I, FieldMeta as J, Aggregation as K, DonutValueContent as L, ChartsLayout as M, ComboChartDef as N, ChartNumberFormat as O, CustomChartDef as P, ModelDef as Q, FieldSpec as R, BarValuePosition as S, CardStyle as T, resolveChartTypes as U, TableChartDef as V, validateLayout as W, MeasureDef as X, FormulaMeasure as Y, MeasureMeta as Z, ModelValidation as _, DashboardBatchQuery as a, modelMeta as at, SchemaTable as b, DashboardFilters as c, DashboardResult as d, RatioMeasure as et, DashboardRow as f, ModelColumns as g, FilterValue as h, ValueRule as i, ValueFormat as it, ChartTypes as j, ChartType as k, DashboardQuery as l, DevtoolsCapabilities as m, ColumnVerticalAlign as n, SumMeasure as nt, DashboardBatchResult as o, validateModel as ot, DashboardSnapshot as p, DimensionDef as q, FormatRule as r, TableDef as rt, DashboardDevtoolsRequest as s, ColumnAlign as t, RelationshipDef as tt, DashboardQueryDebug as u, ProfileStep as v, CanvasDef as w, BarChartDef as x, SaveModelResult as y, FilterDef as z };
715
+ //# sourceMappingURL=formatRules-DTKbov47.d.ts.map