@bluemarble/bi 0.2.1 → 0.3.1

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":"layout-DTR4vI-I.js","names":["isRecord"],"sources":["../src/star.ts","../src/model.ts","../src/codegen.ts","../src/layout.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). Só visual: não muda nenhuma consulta. Card sem posição entra no layout automático.\n */\n diagram?: Record<string, DiagramPosition>\n}\n\nexport type DiagramPosition = { x: number; y: 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 dimensions: Record<string, FieldMeta & { type: DimensionDef['type'] }>\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 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, ...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 } } com números']\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) => isRecord(point) && Number.isFinite(point.x) && Number.isFinite(point.y)\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 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\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/** 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(\n Object.keys(model.tables).length\n ? ` tables: {\\n${entries(model.tables, tableCode)}\\n },`\n : ' tables: {},'\n )\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: {\\n${entries(model.dimensions, (dim) =>\n [`{ sql: ${literal(dim.sql)}`, `type: '${dim.type}'`, ...metaEntries(dim)]\n .join(', ')\n .concat(' }')\n )}\\n },`,\n ` measures: {\\n${entries(model.measures, measureCode)}\\n }${model.diagram ? ',' : ''}`\n )\n if (model.diagram)\n lines.push(` diagram: {\\n${entries(model.diagram, ({ x, y }) => `{ x: ${x}, y: ${y} }`)}\\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","/**\n * Layout de gráficos de uma página: lista declarativa (JSON) que o `<DashboardCharts>` renderiza. É\n * JSON — e não JSX — para o devtools conseguir adicionar e editar gráficos pelo navegador e gravar o\n * arquivo de volta. Fica fora de `server/` porque o client e o servidor validam o mesmo formato.\n *\n * Os campos de cada tipo são descritos em `CHART_FIELDS`: é de lá que saem a validação, os avisos\n * de dimensão/medida inexistente e o formulário da aba Layout do devtools. O app registra tipos\n * próprios (`ChartTypeSpec`) no `<DashboardProvider charts>` e no `devtools.chartTypes` do servidor;\n * as funções daqui recebem o registro completo (`resolveChartTypes`).\n */\n\nimport { isAggregate, type ModelDef } from './model'\n\nexport const CHART_TYPES = ['kpi', 'combo', 'bar', 'donut', 'table'] as const\nexport type ChartType = (typeof CHART_TYPES)[number]\n\nexport const CHART_TYPE_LABEL: Record<ChartType, string> = {\n kpi: 'KPI (card)',\n combo: 'Combo (barras + linha)',\n bar: 'Barras',\n donut: 'Rosca',\n table: 'Tabela'\n}\n\n/** Colunas do grid em telas largas; cada gráfico ocupa `span` delas (no celular, a linha toda). */\nexport const LAYOUT_COLUMNS = 12\n\ntype BaseChartDef = {\n /** Identificador estável do gráfico dentro do layout (letras, números, \"-\" e \"_\"). */\n id: string\n title: string\n /** Colunas ocupadas (1 a 12). Padrão: 12. */\n span?: number\n height?: number\n /** Aparência do card deste gráfico (sobrepõe o `cards` do layout). */\n card?: CardStyle\n}\n\nexport type ComboChartDef = BaseChartDef & {\n type: 'combo'\n dimension: string\n bar: string\n line: string\n detail?: string\n barLabel?: string\n lineLabel?: string\n detailLabel?: string\n /** Nome de um formatter passado ao `<DashboardCharts formatters>` (funções não cabem no JSON). */\n formatDimension?: string\n clickToFilter?: boolean\n hideEmpty?: boolean\n maxLabels?: number\n barColor?: string\n lineColor?: string\n}\n\nexport type BarChartDef = BaseChartDef & {\n type: 'bar'\n dimension: string\n /** Uma série por medida. */\n measures: string[]\n orientation?: 'vertical' | 'horizontal'\n stacked?: boolean\n /** `dimension` = ordem da dimensão; `desc`/`asc` = pela primeira medida. */\n sort?: 'dimension' | 'desc' | 'asc'\n /** Só os N primeiros depois de ordenar. */\n limit?: number\n formatDimension?: string\n clickToFilter?: boolean\n hideEmpty?: boolean\n showValues?: boolean\n}\n\nexport type DonutChartDef = BaseChartDef & {\n type: 'donut'\n dimension: string\n measure: string\n /** Fatias além dessa quantidade viram \"Outros\". */\n maxSlices?: number\n formatDimension?: string\n clickToFilter?: boolean\n}\n\nexport type TableChartDef = BaseChartDef & {\n type: 'table'\n dimensions?: string[]\n measures: string[]\n /**\n * Medidas com coluna de participação no total: valor da linha ÷ soma da coluna. Medida que não\n * está em `measures` é consultada só para o percentual.\n */\n percentOf?: string[]\n /** Formatter por dimensão (`{ mes: 'nomeMes' }`). */\n formatDimensions?: Record<string, string>\n /** Linha de total, consultada sem as dimensões (razões saem certas, não somadas). */\n showTotal?: boolean\n /** Coluna (dimensão ou medida) da ordem inicial; o usuário reordena clicando no cabeçalho. */\n sortBy?: string\n sortDirection?: 'asc' | 'desc'\n clickToFilter?: boolean\n}\n\nexport type KpiChartDef = Omit<BaseChartDef, 'height'> & {\n type: 'kpi'\n /** Medida do número grande (total com os filtros da página). */\n measure: string\n /** Medida extra numa linha menor embaixo (ex.: volume, custo/t). */\n secondary?: string\n secondaryLabel?: string\n}\n\n/** Gráficos da lib (`type` em `CHART_TYPES`). */\nexport type BuiltinChartDef =\n | KpiChartDef\n | ComboChartDef\n | BarChartDef\n | DonutChartDef\n | TableChartDef\n\n/** Gráfico de um tipo registrado pelo app: os campos vêm do `ChartTypeSpec` dele. */\nexport type CustomChartDef = BaseChartDef & { type: string; [field: string]: unknown }\n\nexport type ChartDef = BuiltinChartDef | CustomChartDef\n\nexport const isBuiltinChart = (chart: ChartDef): chart is BuiltinChartDef =>\n (CHART_TYPES as readonly string[]).includes(chart.type)\n\n/** Filtro (slicer) da página: múltipla escolha de uma dimensão, com filtro cruzado. */\nexport type FilterDef = {\n dimension: string\n /** Padrão: `label` da dimensão no dicionário. */\n label?: string\n width?: number\n /** Nome de um formatter do `<DashboardFilters formatters>` para as opções. */\n formatLabel?: string\n}\n\n/**\n * Tela de tamanho fixo: o `<DashboardCanvas>` desenha o dashboard nessas dimensões e o escala para\n * caber na largura da tela, mantendo a proporção — os gráficos não se reorganizam (como o \"Ajustar à\n * largura\" do Power BI).\n */\nexport type CanvasDef = {\n /** Largura de projeto, em px (ex.: 1920). */\n width: number\n /** Altura mínima de projeto, em px (ex.: 1080); conteúdo mais alto estende a tela. */\n height?: number\n}\n\n/** Moldura de um gráfico (o card): ausente = padrão do componente. */\nexport type CardStyle = {\n background?: string\n borderColor?: string\n /** Espessura da borda, em px (0 = sem borda). */\n borderWidth?: number\n /** Arredondamento dos cantos, em px. */\n borderRadius?: number\n /** Sombra (elevation do MUI, 0 a 24). */\n shadow?: number\n /** Espaço interno, em px. */\n padding?: number\n titleColor?: string\n /** Tamanho do título, em px. */\n titleSize?: number\n titleAlign?: 'left' | 'center' | 'right'\n}\n\n/** Cards de todos os gráficos do layout: estilo padrão e espaço entre eles. */\nexport type CardsStyle = CardStyle & {\n /** Espaço entre os cards, em px. */\n gap?: number\n}\n\nexport type ChartsLayout = {\n /** Nome do arquivo (`<layoutsDir>/<id>.json`) e chave do rascunho no devtools. */\n id: string\n /** Ausente = responsivo (12 colunas em telas largas, um gráfico por linha no celular). */\n canvas?: CanvasDef\n /** Estilo padrão dos cards e espaço entre eles; cada gráfico sobrepõe com `card`. */\n cards?: CardsStyle\n /** Renderizados pelo `<DashboardFilters>`, na ordem. */\n filters?: FilterDef[]\n charts: ChartDef[]\n}\n\nexport const LAYOUT_ID = /^[A-Za-z0-9_-]{1,80}$/\n\n// --- campos por tipo ---\n\nexport type FieldSpec = {\n label: string\n hint?: string\n /** Agrupamento no formulário do devtools. */\n section: 'Gráfico' | 'Dados' | 'Aparência' | 'Filtro' | 'Tela' | 'Card'\n required?: boolean\n /** Valor que o componente usa quando o campo não está no JSON. */\n default?: unknown\n} & (\n | { kind: 'text' }\n | { kind: 'color' }\n | { kind: 'boolean' }\n | { kind: 'integer'; min: number; max: number }\n | { kind: 'enum'; values: readonly string[]; labels?: Record<string, string> }\n /** Nome de dimensão/medida do modelo (uma ou uma lista). */\n | { kind: 'dimension' | 'measure' }\n | { kind: 'dimensions' | 'measures' }\n /** Nome de um formatter do `<DashboardCharts formatters>`. */\n | { kind: 'formatter' }\n /** `{ dimensão: formatter }`. */\n | { kind: 'formatters' }\n /** Uma das dimensões/medidas escolhidas no próprio gráfico. */\n | { kind: 'column' }\n)\n\nconst common = (height: number): Record<string, FieldSpec> => ({\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\n span: {\n kind: 'integer',\n min: 1,\n max: LAYOUT_COLUMNS,\n default: LAYOUT_COLUMNS,\n label: 'Largura',\n hint: 'Colunas ocupadas em telas largas; no celular, cada gráfico ocupa a linha toda (menos com a tela de tamanho fixo)',\n section: 'Gráfico'\n },\n height: {\n kind: 'integer',\n min: 120,\n max: 2000,\n default: height,\n label: 'Altura (px)',\n section: 'Gráfico'\n }\n})\n\nconst formatDimension: FieldSpec = {\n kind: 'formatter',\n label: 'Formatar dimensão',\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\n section: 'Dados'\n}\nconst clickToFilter: FieldSpec = {\n kind: 'boolean',\n default: false,\n label: 'Clique filtra o dashboard',\n hint: 'Ctrl/Cmd+clique acumula; clicar de novo no único selecionado limpa',\n section: 'Dados'\n}\nconst hideEmpty: FieldSpec = {\n kind: 'boolean',\n default: true,\n label: 'Esconder grupos vazios',\n section: 'Dados'\n}\n\nexport const CHART_FIELDS: Record<ChartType, Record<string, FieldSpec>> = {\n kpi: {\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\n // KPI tem a altura do conteúdo: só título e largura\n span: common(0).span as FieldSpec,\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\n secondary: {\n kind: 'measure',\n label: 'Medida secundária',\n hint: 'Linha menor embaixo do número (ex.: volume, custo/t)',\n section: 'Dados'\n },\n secondaryLabel: {\n kind: 'text',\n label: 'Rótulo da secundária',\n hint: 'Padrão: rótulo da medida no dicionário',\n section: 'Aparência'\n }\n },\n combo: {\n ...common(360),\n maxLabels: {\n kind: 'integer',\n min: 0,\n max: 1000,\n default: 24,\n label: 'Máx. rótulos',\n hint: 'Acima dessa quantidade de barras, os valores aparecem só no tooltip',\n section: 'Gráfico'\n },\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (eixo X)', section: 'Dados' },\n formatDimension,\n bar: { kind: 'measure', required: true, label: 'Barra', section: 'Dados' },\n line: { kind: 'measure', required: true, label: 'Linha', section: 'Dados' },\n detail: { kind: 'measure', label: 'Detalhe (2ª linha do rótulo)', section: 'Dados' },\n clickToFilter,\n hideEmpty,\n barLabel: { kind: 'text', label: 'Rótulo da barra', section: 'Aparência' },\n lineLabel: { kind: 'text', label: 'Rótulo da linha', section: 'Aparência' },\n detailLabel: { kind: 'text', label: 'Rótulo do detalhe', section: 'Aparência' },\n barColor: { kind: 'color', default: '#F58220', label: 'Cor da barra', section: 'Aparência' },\n lineColor: { kind: 'color', default: '#555555', label: 'Cor da linha', section: 'Aparência' }\n },\n bar: {\n ...common(360),\n dimension: {\n kind: 'dimension',\n required: true,\n label: 'Dimensão (categorias)',\n section: 'Dados'\n },\n formatDimension,\n measures: {\n kind: 'measures',\n required: true,\n label: 'Medidas (uma série cada)',\n section: 'Dados'\n },\n sort: {\n kind: 'enum',\n values: ['dimension', 'desc', 'asc'],\n labels: { dimension: 'pela dimensão', desc: 'maior primeiro', asc: 'menor primeiro' },\n default: 'dimension',\n label: 'Ordem',\n hint: 'Maior/menor primeiro usa a primeira medida',\n section: 'Dados'\n },\n limit: {\n kind: 'integer',\n min: 1,\n max: 1000,\n label: 'Só os N primeiros',\n hint: 'Aplicado depois de ordenar (ex.: top 10 navios)',\n section: 'Dados'\n },\n clickToFilter,\n hideEmpty,\n orientation: {\n kind: 'enum',\n values: ['vertical', 'horizontal'],\n default: 'vertical',\n label: 'Orientação',\n hint: 'Horizontal cabe nomes longos (navios, funções)',\n section: 'Aparência'\n },\n stacked: {\n kind: 'boolean',\n default: false,\n label: 'Empilhar séries',\n hint: 'Só faz sentido com medidas somáveis (partes de um total)',\n section: 'Aparência'\n },\n showValues: { kind: 'boolean', default: true, label: 'Mostrar valores', section: 'Aparência' }\n },\n donut: {\n ...common(300),\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (fatias)', section: 'Dados' },\n formatDimension,\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\n maxSlices: {\n kind: 'integer',\n min: 2,\n max: 8,\n default: 6,\n label: 'Máx. fatias',\n hint: 'As menores além dessa quantidade viram \"Outros\"',\n section: 'Dados'\n },\n clickToFilter\n },\n table: {\n ...common(400),\n dimensions: { kind: 'dimensions', label: 'Dimensões (linhas)', section: 'Dados' },\n formatDimensions: {\n kind: 'formatters',\n label: 'Formatar dimensões',\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\n section: 'Dados'\n },\n measures: { kind: 'measures', required: true, label: 'Medidas (colunas)', section: 'Dados' },\n percentOf: {\n kind: 'measures',\n label: '% do total',\n hint: 'Coluna extra com a participação de cada linha: valor da linha ÷ soma da coluna. Faz sentido com medidas somáveis (custo, volume), não com razões.',\n section: 'Dados'\n },\n sortBy: {\n kind: 'column',\n label: 'Ordenar por',\n hint: 'Ordem inicial; quem usa reordena clicando no cabeçalho',\n section: 'Dados'\n },\n sortDirection: {\n kind: 'enum',\n values: ['desc', 'asc'],\n labels: { desc: 'decrescente', asc: 'crescente' },\n default: 'desc',\n label: 'Direção',\n section: 'Dados'\n },\n showTotal: { kind: 'boolean', default: true, label: 'Linha de total', section: 'Dados' },\n clickToFilter: {\n ...clickToFilter,\n hint: 'Clicar numa linha filtra pela primeira dimensão'\n }\n }\n}\n\n// --- registro de tipos ---\n\n/**\n * Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e\n * `height` entram sozinhos (`height: null` = sem altura, como o KPI) — `fields` tem só os do\n * componente. Objeto puro (sem React): o mesmo arquivo serve o client e o servidor.\n *\n * ```ts\n * export const rankingType = {\n * label: 'Ranking',\n * height: 300,\n * fields: {\n * dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Dados' },\n * measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' }\n * }\n * } satisfies ChartTypeSpec\n * ```\n */\nexport type ChartTypeSpec = {\n label: string\n fields: Record<string, FieldSpec>\n /** Altura padrão em px (padrão: 300); `null` = o componente tem a altura do conteúdo. */\n height?: number | null\n}\n\n/** Registro completo (lib + app), com os campos comuns já incluídos. Ver `resolveChartTypes`. */\nexport type ChartTypes = Record<string, { label: string; fields: Record<string, FieldSpec> }>\n\nexport const BUILTIN_CHART_TYPES: ChartTypes = Object.fromEntries(\n CHART_TYPES.map((type) => [type, { label: CHART_TYPE_LABEL[type], fields: CHART_FIELDS[type] }])\n)\n\n/**\n * Junta os tipos da lib com os do app. Nome igual a um tipo da lib ou fora do padrão de id é erro\n * (aparece na subida, não no meio da edição).\n */\nexport function resolveChartTypes(custom: Record<string, ChartTypeSpec> = {}): ChartTypes {\n const types: ChartTypes = { ...BUILTIN_CHART_TYPES }\n for (const [name, spec] of Object.entries(custom)) {\n if (BUILTIN_CHART_TYPES[name])\n throw new Error(`Tipo de gráfico \"${name}\" já existe na lib: escolha outro nome`)\n if (!LAYOUT_ID.test(name))\n throw new Error(`Tipo de gráfico \"${name}\": use letras, números, \"-\" e \"_\"`)\n const { title, span, height } = common(spec.height ?? 300)\n const base = spec.height === null ? { title, span } : { title, span, height }\n types[name] = {\n label: spec.label,\n fields: { ...base, ...spec.fields } as Record<string, FieldSpec>\n }\n }\n return types\n}\n\n/**\n * Props que o componente de um tipo do app recebe: os campos do gráfico no JSON (com os `default`\n * dos campos aplicados e os formatters já trocados pelas funções), sem `id`, `type`, `title` e\n * `span`, mais o `queryLabel` (nome da consulta no devtools).\n */\nexport function customChartProps(\n chart: ChartDef,\n types: ChartTypes,\n formatters: Record<string, unknown>\n): Record<string, unknown> {\n const values = chart as Record<string, unknown>\n const props: Record<string, unknown> = {}\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n if (key === 'title' || key === 'span') continue\n const value = values[key] ?? spec.default\n if (value === undefined) continue\n if (spec.kind === 'formatter') props[key] = formatters[value as string]\n else if (spec.kind === 'formatters')\n props[key] = Object.fromEntries(\n Object.entries(value as Record<string, string>)\n .filter(([, name]) => formatters[name])\n .map(([dimension, name]) => [dimension, formatters[name]])\n )\n else props[key] = value\n }\n return { ...props, queryLabel: `${chart.title || chart.id} · ${chart.type}` }\n}\n\nexport const FILTER_FIELDS: Record<string, FieldSpec> = {\n dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Filtro' },\n label: {\n kind: 'text',\n label: 'Rótulo',\n hint: 'Padrão: rótulo da dimensão no dicionário',\n section: 'Filtro'\n },\n width: {\n kind: 'integer',\n min: 80,\n max: 800,\n default: 200,\n label: 'Largura (px)',\n section: 'Filtro'\n },\n formatLabel: {\n kind: 'formatter',\n label: 'Formatar opções',\n hint: 'Formatters passados ao <DashboardFilters formatters={{ ... }}> da página',\n section: 'Filtro'\n }\n}\n\nexport const CANVAS_FIELDS: Record<string, FieldSpec> = {\n width: {\n kind: 'integer',\n min: 320,\n max: 7680,\n required: true,\n label: 'Largura (px)',\n section: 'Tela'\n },\n height: {\n kind: 'integer',\n min: 200,\n max: 4320,\n label: 'Altura mínima (px)',\n hint: 'Conteúdo mais alto que isso estende a tela',\n section: 'Tela'\n }\n}\n\n/** Campos de `CardStyle` (o `card` de um gráfico). */\nexport const CARD_FIELDS: Record<string, FieldSpec> = {\n background: { kind: 'color', default: '#ffffff', label: 'Fundo', section: 'Card' },\n borderColor: { kind: 'color', default: '#e0e0e0', label: 'Cor da borda', section: 'Card' },\n borderWidth: {\n kind: 'integer',\n min: 0,\n max: 8,\n default: 1,\n label: 'Borda (px)',\n hint: '0 = sem borda',\n section: 'Card'\n },\n borderRadius: {\n kind: 'integer',\n min: 0,\n max: 40,\n default: 4,\n label: 'Cantos (px)',\n section: 'Card'\n },\n shadow: {\n kind: 'integer',\n min: 0,\n max: 24,\n default: 0,\n label: 'Sombra',\n hint: 'Elevação do MUI: 0 = sem sombra, 1 a 4 = sutil, acima disso = forte',\n section: 'Card'\n },\n padding: {\n kind: 'integer',\n min: 0,\n max: 64,\n default: 12,\n label: 'Espaço interno (px)',\n section: 'Card'\n },\n titleColor: { kind: 'color', default: '#212121', label: 'Cor do título', section: 'Card' },\n titleSize: {\n kind: 'integer',\n min: 10,\n max: 40,\n default: 16,\n label: 'Tamanho do título (px)',\n section: 'Card'\n },\n titleAlign: {\n kind: 'enum',\n values: ['left', 'center', 'right'],\n labels: { left: 'à esquerda', center: 'centralizado', right: 'à direita' },\n default: 'left',\n label: 'Alinhamento do título',\n section: 'Card'\n }\n}\n\n/** Campos de `CardsStyle` (`layout.cards`): o espaço entre os cards e o estilo padrão deles. */\nexport const CARDS_FIELDS: Record<string, FieldSpec> = {\n gap: {\n kind: 'integer',\n min: 0,\n max: 64,\n default: 12,\n label: 'Espaço entre cards (px)',\n section: 'Card'\n },\n ...CARD_FIELDS\n}\n\n/** Estilo de um card: o padrão do layout com o do gráfico por cima. */\nexport function chartCardStyle(layout: ChartsLayout, chart: ChartDef): CardStyle {\n const { gap: _gap, ...defaults } = layout.cards ?? {}\n return { ...defaults, ...chart.card }\n}\n\n/** Tamanhos comuns de tela para o devtools. */\nexport const CANVAS_PRESETS: CanvasDef[] = [\n { width: 1920, height: 1080 },\n { width: 1600, height: 900 },\n { width: 1366, height: 768 },\n { width: 1280, height: 720 }\n]\n\n// --- validação ---\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isName = (value: unknown): value is string => typeof value === 'string' && !!value\n\nfunction fieldError(spec: FieldSpec, value: unknown): string | null {\n if (value === undefined) return spec.required ? 'é obrigatório' : null\n switch (spec.kind) {\n case 'text':\n return typeof value === 'string' ? null : 'precisa ser texto'\n case 'color':\n return typeof value === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(value)\n ? null\n : 'precisa ser uma cor (#rrggbb)'\n case 'boolean':\n return typeof value === 'boolean' ? null : 'precisa ser true/false'\n case 'integer':\n return typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= spec.min &&\n value <= spec.max\n ? null\n : `precisa ser um inteiro de ${spec.min} a ${spec.max}`\n case 'enum':\n return spec.values.includes(value as string) ? null : `precisa ser ${spec.values.join(', ')}`\n case 'dimension':\n case 'measure':\n case 'formatter':\n case 'column':\n return isName(value) ? null : spec.required ? 'é obrigatório' : 'precisa ser texto'\n case 'dimensions':\n case 'measures':\n if (!Array.isArray(value) || !value.every(isName)) return 'precisa ser uma lista de nomes'\n if (spec.required && !value.length) return 'precisa de pelo menos um item'\n return new Set(value).size === value.length ? null : 'tem item repetido'\n case 'formatters':\n return isRecord(value) && Object.values(value).every(isName)\n ? null\n : 'precisa ser { dimensão: formatter }'\n }\n}\n\n/**\n * Problemas de forma do layout (vazio = válido). Confere a forma do objeto porque o rascunho do\n * devtools chega como JSON da request.\n */\nexport function validateLayout(\n layout: ChartsLayout,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): string[] {\n if (!isRecord(layout)) return ['layout inválido']\n if (typeof layout.id !== 'string' || !LAYOUT_ID.test(layout.id))\n return ['`id` do layout: letras, números, \"-\" e \"_\" (até 80)']\n if (!Array.isArray(layout.charts)) return ['`charts` precisa ser uma lista']\n\n const errors: string[] = []\n const ids = new Set<string>()\n layout.charts.forEach((chart: unknown, index) => {\n const context = `gráfico ${index + 1}`\n if (!isRecord(chart)) {\n errors.push(`${context} inválido`)\n return\n }\n if (typeof chart.id !== 'string' || !LAYOUT_ID.test(chart.id))\n errors.push(`${context}: \\`id\\` inválido`)\n else if (ids.has(chart.id)) errors.push(`${context}: id repetido \"${chart.id}\"`)\n else ids.add(chart.id)\n\n const fields =\n typeof chart.type === 'string' && Object.hasOwn(types, chart.type)\n ? types[chart.type]?.fields\n : undefined\n if (!fields) {\n errors.push(`${context}: tipo inválido \"${chart.type}\"`)\n return\n }\n if (typeof chart.title !== 'string') errors.push(`${context}: \\`title\\` precisa ser texto`)\n const rest = Object.fromEntries(Object.entries(fields).filter(([key]) => key !== 'title'))\n errors.push(...fieldsErrors(context, rest, chart, ['id', 'type', 'title', 'card']))\n if (chart.card !== undefined) {\n if (!isRecord(chart.card)) errors.push(`${context}: \\`card\\` precisa ser um objeto`)\n else errors.push(...fieldsErrors(`${context}: card`, CARD_FIELDS, chart.card))\n }\n })\n\n if (layout.cards !== undefined) {\n if (!isRecord(layout.cards)) errors.push('`cards` precisa ser um objeto')\n else errors.push(...fieldsErrors('cards', CARDS_FIELDS, layout.cards))\n }\n\n if (layout.canvas !== undefined) {\n if (!isRecord(layout.canvas)) errors.push('`canvas` precisa ser { width, height? }')\n else errors.push(...fieldsErrors('canvas', CANVAS_FIELDS, layout.canvas))\n }\n\n if (layout.filters !== undefined) {\n if (!Array.isArray(layout.filters)) return [...errors, '`filters` precisa ser uma lista']\n const dimensions = new Set<string>()\n layout.filters.forEach((filter: unknown, index) => {\n const context = `filtro ${index + 1}`\n if (!isRecord(filter)) {\n errors.push(`${context} inválido`)\n return\n }\n errors.push(...fieldsErrors(context, FILTER_FIELDS, filter))\n if (isName(filter.dimension)) {\n if (dimensions.has(filter.dimension))\n errors.push(`${context}: dimensão repetida \"${filter.dimension}\"`)\n dimensions.add(filter.dimension)\n }\n })\n }\n return errors\n}\n\nfunction fieldsErrors(\n context: string,\n fields: Record<string, FieldSpec>,\n values: Record<string, unknown>,\n extraKeys: string[] = []\n) {\n const errors: string[] = []\n for (const [key, spec] of Object.entries(fields)) {\n const error = fieldError(spec, values[key])\n if (error) errors.push(`${context}: \\`${key}\\` ${error}`)\n }\n const known = new Set([...extraKeys, ...Object.keys(fields)])\n for (const key of Object.keys(values))\n if (!known.has(key)) errors.push(`${context}: campo desconhecido \\`${key}\\``)\n return errors\n}\n\n/** Dimensões, medidas e formatters que o gráfico referencia (lidos pelos campos do tipo). */\nexport const chartReferences = (chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) =>\n fieldReferences(types[chart.type]?.fields ?? {}, chart)\n\n/** Idem para um filtro da página. */\nexport const filterReferences = (filter: FilterDef) => fieldReferences(FILTER_FIELDS, filter)\n\nfunction fieldReferences(fields: Record<string, FieldSpec>, item: object) {\n const refs = { dimensions: [] as string[], measures: [] as string[], formatters: [] as string[] }\n const values = item as Record<string, unknown>\n for (const [key, spec] of Object.entries(fields)) {\n const value = values[key]\n if (value === undefined || value === '') continue\n if (spec.kind === 'dimension') refs.dimensions.push(value as string)\n if (spec.kind === 'dimensions') refs.dimensions.push(...(value as string[]))\n if (spec.kind === 'measure') refs.measures.push(value as string)\n if (spec.kind === 'measures') refs.measures.push(...(value as string[]))\n if (spec.kind === 'formatter') refs.formatters.push(value as string)\n if (spec.kind === 'formatters') refs.formatters.push(...Object.values(value as object))\n }\n return refs\n}\n\n/** Colunas que o gráfico mostra (para `sortBy`): as dimensões e medidas escolhidas nele. */\nexport function chartColumns(chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) {\n const { dimensions, measures } = chartReferences(chart, types)\n return [...dimensions, ...measures]\n}\n\n/**\n * Dimensões/medidas que o layout usa e o modelo não tem. Não impede salvar (o modelo pode estar\n * sendo editado junto), mas o gráfico vai mostrar erro.\n */\nexport function layoutModelWarnings(\n layout: ChartsLayout,\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): string[] {\n const warnings: string[] = []\n for (const chart of layout.charts) {\n const name = `\"${chart.title || chart.id}\"`\n const { dimensions, measures } = chartReferences(chart, types)\n for (const dimension of dimensions)\n if (!model.dimensions[dimension]) warnings.push(`${name}: dimensão \"${dimension}\" não existe`)\n for (const measure of measures)\n if (!model.measures[measure]) warnings.push(`${name}: medida \"${measure}\" não existe`)\n // Campos `column` (ex.: `sortBy`) precisam ser uma das colunas escolhidas no gráfico\n const columns = chartColumns(chart, types)\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n const value = (chart as Record<string, unknown>)[key]\n if (spec.kind === 'column' && isName(value) && !columns.includes(value))\n warnings.push(`${name}: \"${value}\" (${key}) não é coluna do gráfico`)\n }\n }\n for (const filter of layout.filters ?? [])\n if (!model.dimensions[filter.dimension])\n warnings.push(\n `filtro \"${filter.label || filter.dimension}\": dimensão \"${filter.dimension}\" não existe`\n )\n return warnings\n}\n\n/**\n * Gráfico novo de um tipo, reaproveitando o que der do anterior (troca de tipo no devtools) ou\n * escolhendo dimensão/medidas do modelo.\n */\nexport function chartOfType(\n type: string,\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\n base: Pick<BaseChartDef, 'id' | 'title' | 'span'> & Partial<ChartDef>,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): ChartDef {\n const { id, title, span, card } = base\n const previous = chartReferences(base as ChartDef, types)\n const measures = Object.entries(model.measures)\n const firstSum = measures.find(([, measure]) => isAggregate(measure))?.[0]\n const firstRatio = measures.find(([, measure]) => measure.type === 'ratio')?.[0]\n const dimension = previous.dimensions[0] ?? Object.keys(model.dimensions)[0] ?? ''\n const measure = previous.measures[0] ?? firstSum ?? measures[0]?.[0] ?? ''\n const shared = { id, title, ...(span !== undefined && { span }), ...(card && { card }) }\n switch (type) {\n case 'kpi':\n return {\n ...shared,\n type,\n measure,\n ...(previous.measures[1] && { secondary: previous.measures[1] })\n }\n case 'combo':\n return {\n ...shared,\n type,\n dimension,\n bar: measure,\n line: previous.measures[1] ?? firstRatio ?? measure\n }\n case 'bar':\n return {\n ...shared,\n type,\n dimension,\n measures: previous.measures.length ? previous.measures : [measure]\n }\n case 'donut':\n return { ...shared, type, dimension, measure }\n case 'table':\n return {\n ...shared,\n type,\n dimensions: previous.dimensions.length ? previous.dimensions : [dimension],\n measures: previous.measures.length ? previous.measures : [measure]\n }\n }\n\n // Tipo do app: preenche os campos obrigatórios, na ordem, com o que já estava escolhido\n const chart: CustomChartDef = { ...shared, type }\n const used = { dimensions: 0, measures: 0 }\n for (const [key, spec] of Object.entries(types[type]?.fields ?? {})) {\n if (!spec.required || key === 'title' || key === 'span') continue\n switch (spec.kind) {\n case 'dimension':\n chart[key] = previous.dimensions[used.dimensions++] ?? dimension\n break\n case 'measure':\n chart[key] = previous.measures[used.measures++] ?? measure\n break\n case 'dimensions':\n chart[key] = previous.dimensions.length ? previous.dimensions : [dimension]\n break\n case 'measures':\n chart[key] = previous.measures.length ? previous.measures : [measure]\n break\n case 'enum':\n chart[key] = spec.default ?? spec.values[0]\n break\n case 'integer':\n chart[key] = spec.default ?? spec.min\n break\n case 'boolean':\n chart[key] = spec.default ?? false\n break\n case 'text':\n case 'color':\n if (spec.default !== undefined) chart[key] = spec.default\n break\n }\n }\n return chart\n}\n\n/** Conteúdo do arquivo `<id>.json`. */\nexport function layoutFileContent(layout: ChartsLayout) {\n return `${JSON.stringify(layout, null, 2)}\\n`\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { A as layoutModelWarnings, B as ModelDef, F as DimensionDef, G as TableDef, H as RatioMeasure, I as FieldMeta, J as validateModel, K as ValueFormat, L as FormulaMeasure, M as validateLayout, N as AggregateMeasure, P as Aggregation, R as MeasureDef, S as ChartsLayout, U as RelationshipDef, W as SumMeasure, a as DashboardResult, b as ChartTypeSpec, j as resolveChartTypes, o as DashboardRow, q as modelMeta, r as DashboardQuery, z as MeasureMeta } from "../types-BO_AQNxv.js";
1
+ import { B as MeasureDef, F as AggregateMeasure, G as RelationshipDef, H as ModelDef, I as Aggregation, J as ValueFormat, K as SumMeasure, L as DimensionDef, M as layoutModelWarnings, N as resolveChartTypes, P as validateLayout, R as FieldMeta, S as ChartTypeSpec, V as MeasureMeta, W as RatioMeasure, X as validateModel, Y as modelMeta, a as DashboardResult, o as DashboardRow, q as TableDef, r as DashboardQuery, w as ChartsLayout, z as FormulaMeasure } from "../types-DUJS2Zmx.js";
2
2
  //#region src/codegen.d.ts
3
3
  /**
4
4
  * Gera o código TS de um modelo (`defineModel({...})`). Usado pelo "Copiar TS" do devtools e pelo
@@ -1,4 +1,4 @@
1
- import { A as referencedTables, C as modelMeta, D as modelEdges, E as measureHome, O as parseColumnRef, S as isAggregate, T as joinPlan, _ as toModelCode, d as layoutModelWarnings, f as resolveChartTypes, g as modelObjectCode, h as modelImports, k as reachFrom, m as canonicalJson, p as validateLayout, u as layoutFileContent, v as AGGREGATIONS, w as validateModel, x as aggregateSql } from "../layout-DvHS8XFz.js";
1
+ import { A as modelEdges, D as validateModel, E as modelMeta, M as reachFrom, N as referencedTables, O as joinPlan, T as isAggregate, _ as canonicalJson, b as toModelCode, g as validateLayout, h as resolveChartTypes, j as parseColumnRef, k as measureHome, m as layoutModelWarnings, p as layoutFileContent, v as modelImports, w as aggregateSql, x as AGGREGATIONS, y as modelObjectCode } from "../layout-DTR4vI-I.js";
2
2
  import { n as diffRows, t as describeDiffs } from "../compare-DloZhg1T.js";
3
3
  //#region src/server/compile.ts
4
4
  var DashboardQueryError = class extends Error {};
@@ -390,6 +390,19 @@ async function handleDevtoolsRequest(context) {
390
390
  rows: [],
391
391
  devtools: { snapshot: await saveSnapshot(context) }
392
392
  };
393
+ if (request.compile) {
394
+ const { sql, params } = compileQuery(context.model, context.query);
395
+ return {
396
+ rows: [],
397
+ devtools: { debug: {
398
+ sql,
399
+ params,
400
+ durationMs: 0,
401
+ cached: false,
402
+ draft: context.model !== context.registered
403
+ } }
404
+ };
405
+ }
393
406
  if (request.explain) {
394
407
  const { sql, params } = compileQuery(context.model, context.query);
395
408
  return {
@@ -741,7 +754,10 @@ async function runQuery(models, query, execute, options = {}) {
741
754
  throw new DashboardQueryError(dbMessage(error));
742
755
  }
743
756
  }
744
- const { rows, sql, params, durationMs, cached } = await run(model);
757
+ const { rows, sql, params, durationMs, cached } = await run(model).catch((error) => {
758
+ if (!devtools || error instanceof DashboardQueryError) throw error;
759
+ throw new DashboardQueryError(dbMessage(error));
760
+ });
745
761
  if (!devtools?.debug) return { rows };
746
762
  return {
747
763
  rows,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/server/compile.ts","../../src/server/defineModel.ts","../../src/server/modelFile.ts","../../src/server/normalize.ts","../../src/server/devtools.ts","../../src/server/runQuery.ts"],"sourcesContent":["import { type AggregateMeasure, aggregateSql, isAggregate } from '../model'\nimport { type Edge, joinPlan, measureHome, modelEdges, reachFrom, referencedTables } from '../star'\nimport type { DashboardQuery } from '../types'\nimport type { ModelDef } from './defineModel'\n\nexport class DashboardQueryError extends Error {}\n\n/** Pedaço da consulta, pronto para o devtools medir: devolve uma linha com `n` (linhas do pedaço). */\nexport type CompiledPart = { name: string; sql: string; params: (string | number)[] }\n\nexport type CompiledQuery = {\n sql: string\n params: (string | number)[]\n /** Pedaços do SQL, para o devtools medir cada um: `fato:<tabela>` (a agregação de cada tabela). */\n parts: CompiledPart[]\n}\n\nexport type CompileOptions = {\n /** `LIMIT` em cada tabela: o devtools valida o SQL do modelo com poucas linhas, sem custo. */\n sampleLimit?: number\n}\n\nconst MAX_FILTER_VALUES = 1000\n\nconst quote = (identifier: string) => `\\`${identifier}\\``\n\n/**\n * Expressão de uma medida. `leaf` diz como sai cada agregação (`sum`, `count`, `avg`...): a coluna\n * já agregada pela tabela de origem.\n */\nfunction measureSql(\n model: ModelDef,\n name: string,\n leaf: (name: string, measure: AggregateMeasure) => string,\n stack: string[] = []\n): string {\n if (stack.includes(name))\n throw new DashboardQueryError(`Medida circular: ${[...stack, name].join(' -> ')}`)\n const measure = model.measures[name]\n if (!measure) throw new DashboardQueryError(`Medida desconhecida: \"${name}\"`)\n if (isAggregate(measure)) return leaf(name, measure)\n const next = [...stack, name]\n const ref = (other: string) => measureSql(model, other, leaf, next)\n if (measure.type === 'ratio')\n return `(${ref(measure.numerator)}) / NULLIF(${ref(measure.denominator)}, 0)`\n return `(${measure.expression.replace(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, other) => `(${ref(other)})`)})`\n}\n\n/** Agregações de que as medidas pedidas dependem (razões e fórmulas abertas). */\nfunction leafMeasures(model: ModelDef, names: string[]): string[] {\n const leaves = new Set<string>()\n for (const name of names)\n measureSql(model, name, (leaf, measure) => {\n leaves.add(leaf)\n return measure.sql\n })\n return [...leaves]\n}\n\ntype Filter = { dimension: string; clause: string; params: (string | number)[] }\n\nfunction compileFilters(model: ModelDef, query: DashboardQuery): Filter[] {\n const filters: Filter[] = []\n for (const [name, values] of Object.entries(query.filters ?? {})) {\n const dimension = model.dimensions[name]\n if (!dimension) throw new DashboardQueryError(`Filtro em dimensão desconhecida: \"${name}\"`)\n if (!Array.isArray(values) || !values.length) continue\n if (values.length > MAX_FILTER_VALUES)\n throw new DashboardQueryError(`Valores demais no filtro \"${name}\"`)\n for (const value of values) {\n const valid = dimension.type === 'number' ? Number.isFinite(value) : typeof value === 'string'\n if (!valid) throw new DashboardQueryError(`Valor inválido no filtro \"${name}\": ${value}`)\n }\n filters.push({\n dimension: name,\n clause: `${dimension.sql} IN (${values.map(() => '?').join(', ')})`,\n params: values\n })\n }\n return filters\n}\n\n/**\n * Transforma uma consulta (dimensões + medidas + filtros) em SQL parametrizado (ver `compileStar`).\n *\n * Nomes de dimensões/medidas só são aceitos se existirem no modelo; valores de filtro vão sempre\n * como parâmetro — nada vindo da request é concatenado no SQL.\n */\nexport function compileQuery(\n model: ModelDef,\n query: DashboardQuery,\n options: CompileOptions = {}\n): CompiledQuery {\n const dimensions = query.dimensions ?? []\n const measures = query.measures ?? []\n if (!dimensions.length && !measures.length)\n throw new DashboardQueryError('Informe ao menos uma dimensão ou medida')\n\n for (const name of dimensions) {\n if (!model.dimensions[name]) throw new DashboardQueryError(`Dimensão desconhecida: \"${name}\"`)\n }\n\n const filters = compileFilters(model, query)\n return compileStar(model, dimensions, measures, filters, options)\n}\n\nconst range = (count: number) => Array.from({ length: count }, (_, index) => index + 1).join(', ')\n\n/**\n * Cada tabela de origem (a das medidas pedidas) vira uma agregação no próprio\n * grão, com LEFT JOIN só nas tabelas que levam às dimensões e filtros usados:\n *\n * SELECT <dimensões>, SUM(<medida>) FROM fPesagem\n * LEFT JOIN dOperacao ON dOperacao.id = fPesagem.navio_operacao_id\n * WHERE <filtros> GROUP BY <dimensões>\n *\n * Com medidas de mais de uma fato, cada uma agrega separada e o resultado é somado por dimensão\n * (`UNION ALL` + `GROUP BY`) — juntar duas fatos antes de somar multiplicaria os valores. Razões e\n * fórmulas são calculadas no fim, sobre os totais.\n *\n * Filtro numa dimensão que a fato não alcança é ignorado por ela (como no Power BI, a medida não é\n * filtrada); agrupar por uma dimensão que ela não alcança é erro.\n */\nfunction compileStar(\n model: ModelDef,\n dimensions: string[],\n measures: string[],\n filters: Filter[],\n { sampleLimit }: CompileOptions\n): CompiledQuery {\n const { tables } = model\n const tableNames = Object.keys(tables)\n const edges = modelEdges(model)\n const tableOf = (sql: string) => referencedTables(sql, tableNames)\n const dimensionTable = (name: string) => tableOf(model.dimensions[name].sql)[0]\n\n // Agregações agrupadas pela tabela onde agregam\n const groups = new Map<string, string[]>()\n const leafOf = (name: string) => model.measures[name] as AggregateMeasure\n for (const leaf of leafMeasures(model, measures)) {\n const measure = model.measures[leaf]\n if (!isAggregate(measure)) continue\n const home = measureHome(tableOf(measure.sql), edges)\n if (!home) throw new DashboardQueryError(`Medida \"${leaf}\" sem tabela de origem`)\n groups.set(home, [...(groups.get(home) ?? []), leaf])\n }\n if (!groups.size) groups.set(dimensionHome(model, dimensions, filters, edges), [])\n\n const source = (name: string) => {\n const sql = tables[name].sql\n return sampleLimit === undefined\n ? `(${sql})`\n : `(SELECT * FROM (${sql}) t LIMIT ${Math.max(0, Math.trunc(sampleLimit))})`\n }\n\n const aggregate = (home: string, leaves: string[]) => {\n const { paths } = reachFrom(home, edges)\n for (const name of dimensions)\n if (!paths.has(dimensionTable(name)))\n throw new DashboardQueryError(\n leaves.length\n ? `A dimensão \"${name}\" (${dimensionTable(name)}) não se relaciona com ${home}, de onde vêm ${leaves.join(', ')}`\n : `As dimensões ${dimensions.join(', ')} não se relacionam entre si`\n )\n const applied = filters.filter((filter) => paths.has(dimensionTable(filter.dimension)))\n const needed = new Set([\n ...dimensions.map(dimensionTable),\n ...applied.map((filter) => dimensionTable(filter.dimension)),\n ...leaves.flatMap((leaf) => tableOf(leafOf(leaf).sql))\n ])\n const joins = joinPlan(home, needed, edges).map(\n ({ table, edge }) =>\n `${edge.inner ? 'JOIN' : 'LEFT JOIN'} ${source(table)} ${quote(table)}\\n ON ${quote(table)}.${quote(edge.to.column)} = ${quote(edge.from.table)}.${quote(edge.from.column)}`\n )\n const select = [\n ...dimensions.map((name) => `${model.dimensions[name].sql} AS ${quote(name)}`),\n ...leaves.map((leaf) => `${aggregateSql(leafOf(leaf))} AS ${quote(leaf)}`)\n ]\n const sql = [\n `SELECT ${select.join(',\\n ')}`,\n `FROM ${source(home)} ${quote(home)}`,\n ...joins,\n applied.length ? `WHERE ${applied.map((filter) => filter.clause).join(' AND ')}` : '',\n dimensions.length ? `GROUP BY ${range(dimensions.length)}` : ''\n ]\n .filter(Boolean)\n .join('\\n')\n return { home, sql, params: applied.flatMap((filter) => filter.params) }\n }\n\n const aggregates = [...groups].map(([home, leaves]) => aggregate(home, leaves))\n const parts = aggregates.map(({ home, sql, params }) => ({\n name: `fato:${home}`,\n sql: `SELECT COUNT(*) AS n FROM (${sql}) g`,\n params\n }))\n const orderBy = dimensions.length ? `\\nORDER BY ${range(dimensions.length)}` : ''\n\n // Uma tabela só: as medidas saem direto da agregação\n if (aggregates.length === 1) {\n const [only] = aggregates\n if (!measures.length || measures.every((name) => isAggregate(model.measures[name])))\n return { sql: `${only.sql}${orderBy}`, params: only.params, parts }\n }\n\n // Várias (ou razões/fórmulas): junta as colunas já agregadas e calcula as medidas por cima. Cada\n // fato tem uma linha por grupo e as colunas das outras vêm NULL, então o SUM de fora só pega o\n // valor da própria fato — vale para qualquer agregação (média, contagem distinta...), sem reagregar\n const columns = [...dimensions, ...[...groups.values()].flat()]\n const union = aggregates\n .map(({ home }, index) => {\n const own = new Set(groups.get(home))\n const select = columns.map((column) =>\n dimensions.includes(column) || own.has(column) ? quote(column) : `NULL AS ${quote(column)}`\n )\n return `SELECT ${select.join(', ')} FROM ${quote(`__fato${index}`)}`\n })\n .join('\\n UNION ALL ')\n const leaf = (name: string) => `SUM(${quote('__u')}.${quote(name)})`\n const select = [\n ...dimensions.map((name) => `${quote('__u')}.${quote(name)} AS ${quote(name)}`),\n ...measures.map((name) => `${measureSql(model, name, leaf)} AS ${quote(name)}`)\n ]\n const sql = [\n `WITH ${aggregates.map(({ sql }, index) => `${quote(`__fato${index}`)} AS (\\n${sql}\\n)`).join(',\\n')}`,\n `SELECT ${select.join(',\\n ')}`,\n `FROM (\\n ${union}\\n) ${quote('__u')}`,\n dimensions.length ? `GROUP BY ${range(dimensions.length)}${orderBy}` : ''\n ]\n .filter(Boolean)\n .join('\\n')\n return { sql, params: aggregates.flatMap((item) => item.params), parts }\n}\n\n/**\n * Consulta só de dimensões (ex.: opções de um filtro): parte da tabela que alcança todas as\n * dimensões pedidas e o maior número de filtros ativos — com \"Mês = Janeiro\", a lista de navios sai\n * da fato que liga navio e calendário, e só mostra quem tem lançamento em janeiro. Sem filtro, a\n * própria tabela da dimensão (lista tudo, mesmo sem lançamento).\n */\nfunction dimensionHome(\n model: ModelDef,\n dimensions: string[],\n filters: Filter[],\n edges: Edge[]\n): string {\n const tableNames = Object.keys(model.tables)\n const tableOf = (name: string) => referencedTables(model.dimensions[name].sql, tableNames)[0]\n const own = tableOf(dimensions[0])\n const targets = dimensions.map(tableOf)\n const filterTables = filters.map((filter) => tableOf(filter.dimension))\n let best: { table: string; score: number } | null = null\n // A tabela da própria dimensão primeiro: em empate, ela ganha\n for (const table of [own, ...tableNames.filter((name) => name !== own)]) {\n const { paths } = reachFrom(table, edges)\n if (!targets.every((target) => paths.has(target))) continue\n const score = filterTables.filter((target) => paths.has(target)).length\n if (!best || score > best.score) best = { table, score }\n }\n return best?.table ?? own\n}\n","/**\n * Modelo semântico de um dashboard: o equivalente ao modelo do Power BI (tabelas, relacionamentos e\n * medidas DAX), só que versionado em código.\n *\n * - `tables`: tabelas fato (lançamentos, lado N) e dimensão (cadastros, lado 1). O SQL é a consulta\n * de origem — tabela, view ou SELECT, sem agregar.\n * - `relationships`: N:1 (`from: 'fato.coluna'`, `to: 'dimensao.coluna'`); o filtro vai do lado 1\n * para o lado N. Cada medida agrega no grão da própria tabela, então juntar fatos não duplica\n * valores (o problema de \"fan-out\").\n * - `dimensions`: por onde se agrupa e filtra, citando `tabela.coluna` de uma tabela.\n * - `measures`: agregações (`sum`, `count`, `countDistinct`, `avg`, `min`, `max`) sobre\n * `tabela.coluna`, razões entre medidas (`ratio`) ou fórmulas (`formula`).\n *\n * Dialeto: MySQL/MariaDB.\n */\n\nimport {\n type AggregateMeasure,\n type Aggregation,\n type FormulaMeasure,\n type MeasureMeta,\n type ModelDef,\n type RatioMeasure,\n type SumMeasure,\n validateModel\n} from '../model'\n\nexport type {\n AggregateMeasure,\n Aggregation,\n DimensionDef,\n FieldMeta,\n FormulaMeasure,\n MeasureDef,\n MeasureMeta,\n ModelDef,\n RatioMeasure,\n RelationshipDef,\n SumMeasure,\n TableDef\n} from '../model'\n\nexport const sum = (sql: string, meta?: MeasureMeta): SumMeasure => ({ ...meta, type: 'sum', sql })\n\nconst aggregate =\n <T extends Aggregation>(type: T) =>\n (sql: string, meta?: MeasureMeta): AggregateMeasure & { type: T } => ({ ...meta, type, sql })\n\n/** `COUNT(sql)`: valores não nulos. Para contar linhas, use uma coluna que nunca é nula (a chave). */\nexport const count = aggregate('count')\n/** `COUNT(DISTINCT sql)`: ex. quantas operações têm lançamento (`fVeiculo.navio_operacao_id`). */\nexport const countDistinct = aggregate('countDistinct')\n/** `AVG(sql)`: média das linhas (no estrela, das linhas da própria tabela, não de totais). */\nexport const avg = aggregate('avg')\nexport const min = aggregate('min')\nexport const max = aggregate('max')\n\n/** `numerator / denominator`, ambos nomes de medidas do mesmo modelo (nulo quando o divisor é 0). */\nexport const ratio = (\n numerator: string,\n denominator: string,\n meta?: MeasureMeta\n): RatioMeasure => ({\n ...meta,\n type: 'ratio',\n numerator,\n denominator\n})\n\n/**\n * Combina medidas já agregadas: `{nome}` vira o valor da medida. Ex.:\n * `formula('COALESCE({custoMmo}, 0) + COALESCE({custoAppa}, 0)')` — o jeito de somar medidas de\n * fatos diferentes no modelo estrela.\n */\nexport const formula = (expression: string, meta?: MeasureMeta): FormulaMeasure => ({\n ...meta,\n type: 'formula',\n expression\n})\n\n/** Valida o modelo na carga do módulo: erro de definição aparece no deploy, não na primeira query. */\nexport function defineModel<const M extends ModelDef>(model: M): M {\n const errors = validateModel(model)\n if (errors.length) throw new Error(`[${model.name}] ${errors.join('; ')}`)\n return model\n}\n","import { modelImports, modelObjectCode } from '../codegen'\nimport { AGGREGATIONS, type ModelDef } from '../model'\n\n/**\n * Troca, dentro do arquivo do modelo, só o objeto passado ao `defineModel(...)` e o import do\n * `@bluemarble/bi/server`. O resto do arquivo (JSDoc do modelo, outros exports) fica como está.\n */\n\nexport class ModelFileError extends Error {}\n\n/** Índice do `)` que fecha o `(` em `open`, pulando strings, templates e comentários. */\nfunction findClosingParen(source: string, open: number) {\n let depth = 0\n let comments = false\n // Pilha de contexto: '(' / '{' de código, '`' de template, '${' de interpolação\n const stack: string[] = []\n for (let i = open; i < source.length; i++) {\n const char = source[i]\n const next = source[i + 1]\n const inTemplate = stack[stack.length - 1] === '`'\n\n if (inTemplate) {\n if (char === '\\\\') i++\n else if (char === '`') stack.pop()\n else if (char === '$' && next === '{') {\n stack.push('${')\n i++\n }\n continue\n }\n if (char === \"'\" || char === '\"') {\n for (i++; i < source.length && source[i] !== char; i++) if (source[i] === '\\\\') i++\n continue\n }\n if (char === '/' && next === '/') {\n comments = true\n while (i < source.length && source[i] !== '\\n') i++\n continue\n }\n if (char === '/' && next === '*') {\n comments = true\n i = source.indexOf('*/', i + 2) + 1\n if (i === 0) break\n continue\n }\n if (char === '`') stack.push('`')\n else if (char === '{') stack.push('{')\n else if (char === '}')\n stack.pop() // fecha '{' ou '${'\n else if (char === '(') depth++\n else if (char === ')' && --depth === 0) return { close: i, comments }\n }\n throw new ModelFileError('Não encontrei o fim da chamada `defineModel(...)`')\n}\n\nexport function replaceModelInFile(source: string, model: ModelDef) {\n const warnings: string[] = []\n const call = new RegExp(`export\\\\s+const\\\\s+${model.name}\\\\s*=\\\\s*defineModel(?:<[^>]*>)?\\\\s*\\\\(`)\n const match = call.exec(source)\n if (!match)\n throw new ModelFileError(`O arquivo não tem \\`export const ${model.name} = defineModel(...)\\``)\n\n const open = match.index + match[0].length - 1\n const { close, comments } = findClosingParen(source, open)\n if (comments)\n warnings.push('Comentários dentro do defineModel(...) serão perdidos (os de fora ficam).')\n\n let content = `${source.slice(0, open + 1)}${modelObjectCode(model)}${source.slice(close)}`\n\n // Import: mantém o que mais vier do package e acerta os helpers de medida conforme o uso\n const importRegex = /import\\s*\\{([^}]*)\\}\\s*from\\s*'@bluemarble\\/bi\\/server'/\n const current = importRegex.exec(content)\n const helpers = new Set(['defineModel', 'formula', 'ratio', ...AGGREGATIONS])\n const others = (current?.[1] ?? '')\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name && !helpers.has(name))\n const names = [...others, ...modelImports(model)].sort((a, b) =>\n a.localeCompare(b, 'en', { sensitivity: 'base' })\n )\n const line = `import { ${names.join(', ')} } from '@bluemarble/bi/server'`\n content = current ? content.replace(importRegex, line) : `${line}\\n${content}`\n\n const outside = source.slice(0, match.index) + source.slice(close + 1)\n const measureHelpers = [...helpers].filter((name) => name !== 'defineModel')\n if (new RegExp(`\\\\b(${measureHelpers.join('|')})\\\\(`).test(outside))\n warnings.push('O arquivo usa os helpers de medida fora do modelo; confira o import.')\n\n return { content, warnings }\n}\n","import type { DashboardRow } from '../types'\n\n/** Decimal do Prisma, bigint e afins viram number; datas viram ISO. */\nexport function normalizeValue(value: unknown): string | number | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string' || typeof value === 'number') return value\n if (typeof value === 'bigint') return Number(value)\n if (value instanceof Date) return value.toISOString()\n const number = Number(value)\n return Number.isNaN(number) ? String(value) : number\n}\n\nexport function normalizeRow(row: Record<string, unknown>): DashboardRow {\n const normalized: DashboardRow = {}\n for (const [key, value] of Object.entries(row)) normalized[key] = normalizeValue(value)\n return normalized\n}\n","import {\n type ChartsLayout,\n type ChartTypeSpec,\n layoutFileContent,\n layoutModelWarnings,\n resolveChartTypes,\n validateLayout\n} from '../layout'\nimport { isAggregate, modelMeta, validateModel } from '../model'\nimport { parseColumnRef, referencedTables } from '../star'\nimport type {\n DashboardDevtoolsRequest,\n DashboardQuery,\n DashboardResult,\n DashboardRow,\n DashboardSnapshot,\n ModelColumns,\n ModelValidation,\n ProfileStep,\n SchemaTable\n} from '../types'\nimport { compileQuery, DashboardQueryError } from './compile'\nimport type { ModelDef } from './defineModel'\nimport { ModelFileError, replaceModelInFile } from './modelFile'\nimport { normalizeValue } from './normalize'\n\n/**\n * Pedidos do devtools (`query.devtools`) que não são uma consulta comum. Só chega aqui com\n * `runQuery(..., { devtools })` ligado — tudo isso executa SQL vindo do browser ou escreve em disco.\n */\n\nexport type DevtoolsServerOptions = {\n /**\n * Pasta dos arquivos de modelo (`<pasta>/<nome>.ts`, caminho absoluto). Habilita \"Salvar no\n * arquivo\"; só arquivos que já existem são reescritos.\n */\n modelsDir?: string\n /** Pasta dos snapshots de regressão (`<pasta>/<modelo>/<nome>.json`, caminho absoluto). */\n snapshotsDir?: string\n /**\n * Pasta dos layouts de gráficos (`<pasta>/<id>.json`, caminho absoluto). Habilita \"Salvar no\n * arquivo\" na aba Layout; só arquivos que já existem são reescritos.\n */\n layoutsDir?: string\n /**\n * Tipos de gráfico do app (os mesmos `ChartTypeSpec` do `<DashboardProvider charts>`, sem o\n * componente). Sem eles, salvar um layout com esses tipos é recusado.\n */\n chartTypes?: Record<string, ChartTypeSpec>\n /**\n * Formata o arquivo do modelo antes do preview/gravação (ex.: Biome), para o diff mostrar só o\n * que mudou de fato. Sem isso, o arquivo sai com a formatação do gerador.\n */\n format?: (code: string, path: string) => Promise<string>\n}\n\ntype Executor = (sql: string, params: (string | number)[]) => Promise<Record<string, unknown>[]>\n\ntype Context = {\n registered: ModelDef\n /** Rascunho, se houver; senão o registrado. */\n model: ModelDef\n query: DashboardQuery\n request: DashboardDevtoolsRequest\n execute: Executor\n normalize: (row: Record<string, unknown>) => DashboardRow\n options: DevtoolsServerOptions\n /** Executa a consulta normal (com cache) e devolve as linhas. */\n runRows: (model: ModelDef) => Promise<DashboardRow[]>\n}\n\nconst fs = () => import('node:fs/promises')\n\n/** Mensagem do banco sem o embrulho do Prisma (`Raw query failed. Code: ... Message: ...`). */\nexport function dbMessage(error: unknown) {\n const err = error as { meta?: { message?: string }; message?: string }\n const message = err?.meta?.message ?? err?.message ?? String(error)\n return message.match(/Message: `?([\\s\\S]*?)`?$/)?.[1] ?? message\n}\n\nconst timed = async <T>(load: () => Promise<T>) => {\n const startedAt = performance.now()\n const result = await load()\n return { result, durationMs: Math.round(performance.now() - startedAt) }\n}\n\nconst SLUG = /^[A-Za-z0-9_-]{1,80}$/\n\n/** `<pasta>/<arquivo>`, aceitando pasta com `/` ou `\\` no fim (`path.join` no Windows). */\nconst inDir = (dir: string, file: string) => `${dir.replace(/[\\\\/]+$/, '')}/${file}`\n\n/**\n * Conteúdo final do arquivo: formatado pelo app (se der erro, sai sem formatar) e com o fim de\n * linha do arquivo atual — no Windows o checkout costuma vir com CRLF, e o gerador/formatador\n * devolvem LF, o que faria o diff marcar o arquivo inteiro.\n */\nasync function fileContent(\n raw: string,\n path: string,\n previous: string,\n options: DevtoolsServerOptions\n) {\n const { format } = options\n const formatted = format\n ? await Promise.resolve()\n .then(() => format(raw, path))\n .catch((error) => {\n console.warn('[@bluemarble/bi] format falhou; o arquivo sai sem formatar:', error)\n return raw\n })\n : raw\n const content = formatted.replace(/\\r\\n/g, '\\n')\n return previous.includes('\\r\\n') ? content.replace(/\\n/g, '\\r\\n') : content\n}\n\nexport async function handleDevtoolsRequest(context: Context): Promise<DashboardResult | null> {\n const { request } = context\n if (request.describe) return describe(context)\n if (request.schema) return { rows: [], devtools: { schema: await loadSchema(context.execute) } }\n if (request.columns) {\n const { columns, errors } = await describeColumns(context)\n return { rows: [], devtools: { columns, columnErrors: errors } }\n }\n if (request.validate) return { rows: [], devtools: { validation: await validateSql(context) } }\n if (request.save) return { rows: [], devtools: { save: await saveModel(context) } }\n if (request.saveLayout) return { rows: [], devtools: { save: await saveLayout(context) } }\n if (request.snapshots) return { rows: [], devtools: { snapshots: await listSnapshots(context) } }\n if (request.snapshot) return { rows: [], devtools: { snapshot: await saveSnapshot(context) } }\n if (request.explain) {\n const { sql, params } = compileQuery(context.model, context.query)\n const rows = await context.execute(`EXPLAIN ${sql}`, params)\n return { rows: [], devtools: { explain: rows.map(context.normalize) } }\n }\n if (request.profile) return { rows: [], devtools: { profile: await profile(context) } }\n return null\n}\n\nfunction describe({ registered, options }: Context): DashboardResult {\n return {\n rows: [],\n meta: modelMeta(registered),\n devtools: {\n model: registered,\n capabilities: {\n saveModel: !!options.modelsDir,\n snapshots: !!options.snapshotsDir,\n saveLayout: !!options.layoutsDir,\n chartTypes: Object.keys(options.chartTypes ?? {})\n }\n }\n }\n}\n\nasync function profile({ model, query, execute }: Context): Promise<ProfileStep[]> {\n const { sql, params, parts } = compileQuery(model, query)\n // Em sequência: medições em paralelo disputariam o banco e os tempos não diriam nada\n const steps: ProfileStep[] = []\n for (const part of parts) {\n const { result, durationMs } = await timed(() => execute(part.sql, part.params))\n steps.push({ name: part.name, durationMs, rows: Number(result[0]?.n ?? 0) })\n }\n const total = await timed(() => execute(sql, params))\n steps.push({ name: 'consulta', durationMs: total.durationMs, rows: total.result.length })\n return steps\n}\n\nasync function loadSchema(execute: Executor): Promise<SchemaTable[]> {\n const rows = await execute(\n `SELECT c.TABLE_NAME AS tableName, t.TABLE_TYPE AS tableType,\n c.COLUMN_NAME AS columnName, c.DATA_TYPE AS dataType\n FROM information_schema.COLUMNS c\n JOIN information_schema.TABLES t\n ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME\n WHERE c.TABLE_SCHEMA = DATABASE()\n ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`,\n []\n )\n const tables = new Map<string, SchemaTable>()\n for (const row of rows) {\n const name = String(row.tableName)\n let table = tables.get(name)\n if (!table) {\n table = { name, type: String(row.tableType).includes('VIEW') ? 'view' : 'table', columns: [] }\n tables.set(name, table)\n }\n table.columns.push({ name: String(row.columnName), type: String(row.dataType) })\n }\n return [...tables.values()]\n}\n\n/** Poucas chaves bastam para o banco acusar erro de SQL e para descobrir as colunas. */\nconst SAMPLE_KEYS = 20\n\nconst emptyColumns = (): ModelColumns => ({ tables: {}, numeric: [] })\n\n/** Colunas numéricas da primeira linha (Decimal do Prisma e bigint contam). */\nfunction numericColumns(node: string, row: Record<string, unknown> | undefined) {\n return Object.entries(row ?? {})\n .filter(([, value]) => typeof normalizeValue(value) === 'number')\n .map(([column]) => `${node}.${column}`)\n}\n\n/**\n * Colunas de cada tabela do modelo (uma linha de cada). `errors` traz o que o banco recusou, com as\n * chaves da validação (`table:<nome>`).\n */\nasync function loadColumns(model: ModelDef, execute: Executor) {\n const columns = emptyColumns()\n const errors: Record<string, string> = {}\n const attempt = async (key: string, sql: string) => {\n try {\n return await execute(sql, [])\n } catch (error) {\n errors[key] = dbMessage(error)\n return null\n }\n }\n for (const [name, table] of Object.entries(model.tables)) {\n const rows = await attempt(`table:${name}`, `SELECT * FROM (${table.sql}) t LIMIT 1`)\n if (!rows) continue\n columns.tables[name] = Object.keys(rows[0] ?? {})\n columns.numeric.push(...numericColumns(name, rows[0]))\n }\n return { columns, errors }\n}\n\nasync function describeColumns({ model, execute }: Context) {\n const errors = validateModel(model)\n if (errors.length) throw new DashboardQueryError(`Modelo inválido: ${errors.join('; ')}`)\n return loadColumns(model, execute)\n}\n\nasync function validateSql({ model, execute }: Context): Promise<ModelValidation> {\n const validation: ModelValidation = { errors: {}, warnings: {}, columns: emptyColumns() }\n const modelErrors = validateModel(model)\n if (modelErrors.length) {\n validation.errors.model = modelErrors.join('; ')\n return validation\n }\n\n const attempt = async (key: string, sql: string) => {\n try {\n return await execute(sql, [])\n } catch (error) {\n validation.errors[key] = dbMessage(error)\n return null\n }\n }\n\n const loaded = await loadColumns(model, execute)\n validation.columns = loaded.columns\n Object.assign(validation.errors, loaded.errors)\n const tableNames = Object.keys(model.tables)\n\n for (const [index, relationship] of (model.relationships ?? []).entries()) {\n const key = `relationship:${index}`\n const from = parseColumnRef(relationship.from)\n const to = parseColumnRef(relationship.to)\n if (!from || !to) continue\n const missing = [from, to].find(\n (ref) =>\n validation.columns.tables[ref.table]?.length &&\n !validation.columns.tables[ref.table].includes(ref.column)\n )\n if (missing) {\n validation.errors[key] = `a tabela \"${missing.table}\" não tem a coluna \"${missing.column}\"`\n continue\n }\n // O lado 1 precisa ser único, senão a fato multiplica linhas ao juntar\n const toSql = model.tables[to.table]?.sql\n if (!toSql || validation.errors[`table:${to.table}`]) continue\n const repeated = await attempt(\n key,\n `SELECT \\`${to.column}\\` AS v FROM (${toSql}) t WHERE \\`${to.column}\\` IS NOT NULL GROUP BY 1 HAVING COUNT(*) > 1 LIMIT 1`\n )\n if (repeated?.length)\n validation.errors[key] =\n `o lado 1 (${relationship.to}) tem valor repetido: ${String(normalizeValue(repeated[0].v))}`\n }\n\n for (const [name, dimension] of Object.entries(model.dimensions)) {\n const table = referencedTables(dimension.sql, tableNames)[0]\n if (validation.errors[`table:${table}`]) continue\n const sql = `SELECT ${dimension.sql} AS v FROM (SELECT * FROM (${model.tables[table]?.sql}) t LIMIT ${SAMPLE_KEYS}) \\`${table}\\` WHERE ${dimension.sql} IS NOT NULL LIMIT 1`\n const rows = await attempt(`dimension:${name}`, sql)\n const value = rows?.[0]?.v\n if (value === undefined || value === null) continue\n const isNumber = typeof value === 'number' || typeof value === 'bigint'\n if (dimension.type === 'number' && !isNumber && Number.isNaN(Number(value)))\n validation.warnings[`dimension:${name}`] = `tipo number, mas o banco devolveu \"${value}\"`\n if (dimension.type === 'string' && isNumber)\n validation.warnings[`dimension:${name}`] =\n 'tipo string, mas o banco devolveu número (filtros vão chegar como texto)'\n }\n\n for (const [name, measure] of Object.entries(model.measures)) {\n if (!isAggregate(measure)) continue // razão e fórmula só combinam outras medidas\n const failed = referencedTables(measure.sql, tableNames).some(\n (table) => validation.errors[`table:${table}`]\n )\n if (failed) continue\n const { sql, params } = compileQuery(\n model,\n { model: model.name, measures: [name] },\n { sampleLimit: SAMPLE_KEYS }\n )\n try {\n await execute(sql, params)\n } catch (error) {\n validation.errors[`measure:${name}`] = dbMessage(error)\n }\n }\n\n return validation\n}\n\nasync function saveModel({ registered, model, request, options }: Context) {\n if (!options.modelsDir) throw new DashboardQueryError('O endpoint não configurou `modelsDir`')\n if (!request.model) throw new DashboardQueryError('Nada para salvar: não há rascunho')\n if (model.name !== registered.name)\n throw new DashboardQueryError('O rascunho não pode trocar o nome do modelo')\n\n const path = inDir(options.modelsDir, `${registered.name}.ts`)\n const { readFile, writeFile } = await fs()\n let previous: string\n try {\n previous = await readFile(path, 'utf8')\n } catch {\n throw new DashboardQueryError(`Arquivo do modelo não encontrado: ${path}`)\n }\n\n try {\n const replaced = replaceModelInFile(previous, model)\n const { warnings } = replaced\n const content = await fileContent(replaced.content, path, previous, options)\n const written = request.save === 'write' && content !== previous\n if (written) await writeFile(path, content, 'utf8')\n return { path, previous, content, written, warnings }\n } catch (error) {\n if (error instanceof ModelFileError) throw new DashboardQueryError(error.message)\n throw error\n }\n}\n\nasync function saveLayout({ model, request, options }: Context) {\n if (!options.layoutsDir) throw new DashboardQueryError('O endpoint não configurou `layoutsDir`')\n const { layout, mode } = request.saveLayout ?? ({} as { layout?: ChartsLayout; mode?: string })\n const types = resolveChartTypes(options.chartTypes)\n const errors = layout ? validateLayout(layout, types) : ['Nada para salvar: não há layout']\n if (errors.length) throw new DashboardQueryError(`Layout inválido: ${errors.join('; ')}`)\n const valid = layout as ChartsLayout\n\n const path = inDir(options.layoutsDir, `${valid.id}.json`)\n const { readFile, writeFile } = await fs()\n let previous: string\n try {\n previous = await readFile(path, 'utf8')\n } catch {\n throw new DashboardQueryError(`Arquivo do layout não encontrado: ${path}`)\n }\n\n const content = await fileContent(layoutFileContent(valid), path, previous, options)\n const written = mode === 'write' && content !== previous\n if (written) await writeFile(path, content, 'utf8')\n // Contra o modelo em vigor na página (o rascunho, se houver)\n return { path, previous, content, written, warnings: layoutModelWarnings(valid, model, types) }\n}\n\nconst snapshotDir = (options: DevtoolsServerOptions, model: string) => {\n if (!options.snapshotsDir)\n throw new DashboardQueryError('O endpoint não configurou `snapshotsDir`')\n return inDir(options.snapshotsDir, model)\n}\n\nasync function saveSnapshot({ registered, query, request, options, runRows }: Context) {\n const { name, overwrite } = request.snapshot ?? { name: '' }\n if (!SLUG.test(name))\n throw new DashboardQueryError('Nome do snapshot: letras, números, \"-\" e \"_\" (até 80)')\n const dir = snapshotDir(options, registered.name)\n const path = `${dir}/${name}.json`\n const { mkdir, readFile, writeFile } = await fs()\n\n if (!overwrite) {\n const exists = await readFile(path, 'utf8').then(\n () => true,\n () => false\n )\n if (exists) throw new DashboardQueryError(`Já existe um snapshot \"${name}\"`)\n }\n\n // Sempre o modelo registrado: o snapshot é a referência que o rascunho tem que bater\n const snapshot: DashboardSnapshot = {\n name,\n createdAt: new Date().toISOString(),\n query: {\n model: registered.name,\n dimensions: query.dimensions ?? [],\n measures: query.measures ?? [],\n filters: query.filters ?? {}\n },\n rows: await runRows(registered)\n }\n await mkdir(dir, { recursive: true })\n await writeFile(path, `${JSON.stringify(snapshot, null, 2)}\\n`, 'utf8')\n return { path }\n}\n\nasync function listSnapshots({ registered, options }: Context): Promise<DashboardSnapshot[]> {\n const dir = snapshotDir(options, registered.name)\n const { readdir, readFile } = await fs()\n const files = await readdir(dir).catch(() => [] as string[])\n const snapshots = await Promise.all(\n files\n .filter((file) => file.endsWith('.json'))\n .sort()\n .map(\n async (file) => JSON.parse(await readFile(`${dir}/${file}`, 'utf8')) as DashboardSnapshot\n )\n )\n return snapshots\n}\n","import { modelMeta, validateModel } from '../model'\nimport type { DashboardQuery, DashboardResult, DashboardRow } from '../types'\nimport { compileQuery, DashboardQueryError } from './compile'\nimport type { ModelDef } from './defineModel'\nimport { type DevtoolsServerOptions, dbMessage, handleDevtoolsRequest } from './devtools'\nimport { normalizeRow } from './normalize'\n\n/** Executa o SQL no banco do app (ex.: `(sql, params) => prisma.$queryRawUnsafe(sql, ...params)`). */\nexport type QueryExecutor = (sql: string, params: (string | number)[]) => Promise<unknown[]>\n\nexport type RunQueryOptions = {\n /**\n * Por quanto tempo reaproveitar o resultado de uma mesma consulta (mesmo SQL + parâmetros).\n * Faz o papel do \"refresh\" do Power BI: alternar filtros não recalcula tudo de novo. 0 = sem cache.\n */\n cacheTtlMs?: number\n /**\n * Aceita os pedidos do devtools (`query.devtools`): debug, rascunho de modelo, EXPLAIN, validação\n * de SQL etc. O rascunho é SQL vindo do browser — ligue SÓ em desenvolvimento. Passe as pastas\n * (`modelsDir`, `snapshotsDir`) para habilitar salvar o modelo e os snapshots.\n */\n devtools?: boolean | DevtoolsServerOptions\n}\n\ntype Execution = { rows: DashboardRow[]; durationMs: number }\n\nconst MAX_CACHE_ENTRIES = 500\nconst cache = new Map<string, { expiresAt: number; execution: Promise<Execution> }>()\n\nfunction fromCache(key: string, ttl: number, load: () => Promise<Execution>) {\n const now = Date.now()\n const hit = cache.get(key)\n if (hit && hit.expiresAt > now) return { execution: hit.execution, cached: true }\n\n if (cache.size >= MAX_CACHE_ENTRIES) {\n for (const [k, entry] of cache) if (entry.expiresAt <= now) cache.delete(k)\n if (cache.size >= MAX_CACHE_ENTRIES) cache.delete(cache.keys().next().value as string)\n }\n // Guarda a promise: requests simultâneas da mesma consulta esperam a mesma execução\n const execution = load()\n cache.set(key, { expiresAt: now + ttl, execution })\n execution.catch(() => cache.delete(key))\n return { execution, cached: false }\n}\n\nexport async function runQuery(\n models: Record<string, ModelDef>,\n query: DashboardQuery,\n execute: QueryExecutor,\n options: RunQueryOptions = {}\n): Promise<DashboardResult> {\n // `debug` desligado é só ignorado (a página continua funcionando com um endpoint sem devtools);\n // o resto expõe ou altera o modelo e é recusado\n const { debug, ...privileged } = query?.devtools ?? {}\n const devtools = options.devtools ? query?.devtools : undefined\n if (!options.devtools && Object.values(privileged).some(Boolean))\n throw new DashboardQueryError('Devtools desabilitado neste ambiente')\n\n const registered = models[query?.model]\n if (!registered) throw new DashboardQueryError(`Modelo desconhecido: \"${query?.model}\"`)\n\n const draft = devtools?.model\n if (draft) {\n const errors = validateModel(draft)\n if (errors.length) throw new DashboardQueryError(`Rascunho inválido: ${errors.join('; ')}`)\n }\n const model = draft ?? registered\n\n // Rótulos/formatos (sem SQL): liberado em produção, os componentes usam como padrão\n if (query.meta) return { rows: [], meta: modelMeta(model) }\n\n const ttl = options.cacheTtlMs ?? 0\n const run = async (target: ModelDef) => {\n const { sql, params } = compileQuery(target, query)\n const load = async (): Promise<Execution> => {\n const startedAt = performance.now()\n const rows = (await execute(sql, params)) as Record<string, unknown>[]\n return { durationMs: Math.round(performance.now() - startedAt), rows: rows.map(normalizeRow) }\n }\n // O rascunho muda o SQL, então nunca colide com o cache do modelo registrado\n const { execution, cached } =\n ttl > 0\n ? fromCache(JSON.stringify([sql, params]), ttl, load)\n : { execution: load(), cached: false }\n return { sql, params, cached, ...(await execution) }\n }\n\n if (devtools) {\n if (devtools.clearCache) cache.clear()\n try {\n const handled = await handleDevtoolsRequest({\n registered,\n model,\n query,\n request: devtools,\n execute: (sql, params) => execute(sql, params) as Promise<Record<string, unknown>[]>,\n normalize: normalizeRow,\n options: typeof options.devtools === 'object' ? options.devtools : {},\n runRows: async (target) => (await run(target)).rows\n })\n if (handled) return handled\n } catch (error) {\n // Erro de SQL no devtools é do rascunho/consulta, não do servidor: vira 400 com a mensagem do banco\n if (error instanceof DashboardQueryError) throw error\n throw new DashboardQueryError(dbMessage(error))\n }\n }\n\n const { rows, sql, params, durationMs, cached } = await run(model)\n if (!devtools?.debug) return { rows }\n return { rows, devtools: { debug: { sql, params, durationMs, cached, draft: !!draft } } }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/server/compile.ts","../../src/server/defineModel.ts","../../src/server/modelFile.ts","../../src/server/normalize.ts","../../src/server/devtools.ts","../../src/server/runQuery.ts"],"sourcesContent":["import { type AggregateMeasure, aggregateSql, isAggregate } from '../model'\nimport { type Edge, joinPlan, measureHome, modelEdges, reachFrom, referencedTables } from '../star'\nimport type { DashboardQuery } from '../types'\nimport type { ModelDef } from './defineModel'\n\nexport class DashboardQueryError extends Error {}\n\n/** Pedaço da consulta, pronto para o devtools medir: devolve uma linha com `n` (linhas do pedaço). */\nexport type CompiledPart = { name: string; sql: string; params: (string | number)[] }\n\nexport type CompiledQuery = {\n sql: string\n params: (string | number)[]\n /** Pedaços do SQL, para o devtools medir cada um: `fato:<tabela>` (a agregação de cada tabela). */\n parts: CompiledPart[]\n}\n\nexport type CompileOptions = {\n /** `LIMIT` em cada tabela: o devtools valida o SQL do modelo com poucas linhas, sem custo. */\n sampleLimit?: number\n}\n\nconst MAX_FILTER_VALUES = 1000\n\nconst quote = (identifier: string) => `\\`${identifier}\\``\n\n/**\n * Expressão de uma medida. `leaf` diz como sai cada agregação (`sum`, `count`, `avg`...): a coluna\n * já agregada pela tabela de origem.\n */\nfunction measureSql(\n model: ModelDef,\n name: string,\n leaf: (name: string, measure: AggregateMeasure) => string,\n stack: string[] = []\n): string {\n if (stack.includes(name))\n throw new DashboardQueryError(`Medida circular: ${[...stack, name].join(' -> ')}`)\n const measure = model.measures[name]\n if (!measure) throw new DashboardQueryError(`Medida desconhecida: \"${name}\"`)\n if (isAggregate(measure)) return leaf(name, measure)\n const next = [...stack, name]\n const ref = (other: string) => measureSql(model, other, leaf, next)\n if (measure.type === 'ratio')\n return `(${ref(measure.numerator)}) / NULLIF(${ref(measure.denominator)}, 0)`\n return `(${measure.expression.replace(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, other) => `(${ref(other)})`)})`\n}\n\n/** Agregações de que as medidas pedidas dependem (razões e fórmulas abertas). */\nfunction leafMeasures(model: ModelDef, names: string[]): string[] {\n const leaves = new Set<string>()\n for (const name of names)\n measureSql(model, name, (leaf, measure) => {\n leaves.add(leaf)\n return measure.sql\n })\n return [...leaves]\n}\n\ntype Filter = { dimension: string; clause: string; params: (string | number)[] }\n\nfunction compileFilters(model: ModelDef, query: DashboardQuery): Filter[] {\n const filters: Filter[] = []\n for (const [name, values] of Object.entries(query.filters ?? {})) {\n const dimension = model.dimensions[name]\n if (!dimension) throw new DashboardQueryError(`Filtro em dimensão desconhecida: \"${name}\"`)\n if (!Array.isArray(values) || !values.length) continue\n if (values.length > MAX_FILTER_VALUES)\n throw new DashboardQueryError(`Valores demais no filtro \"${name}\"`)\n for (const value of values) {\n const valid = dimension.type === 'number' ? Number.isFinite(value) : typeof value === 'string'\n if (!valid) throw new DashboardQueryError(`Valor inválido no filtro \"${name}\": ${value}`)\n }\n filters.push({\n dimension: name,\n clause: `${dimension.sql} IN (${values.map(() => '?').join(', ')})`,\n params: values\n })\n }\n return filters\n}\n\n/**\n * Transforma uma consulta (dimensões + medidas + filtros) em SQL parametrizado (ver `compileStar`).\n *\n * Nomes de dimensões/medidas só são aceitos se existirem no modelo; valores de filtro vão sempre\n * como parâmetro — nada vindo da request é concatenado no SQL.\n */\nexport function compileQuery(\n model: ModelDef,\n query: DashboardQuery,\n options: CompileOptions = {}\n): CompiledQuery {\n const dimensions = query.dimensions ?? []\n const measures = query.measures ?? []\n if (!dimensions.length && !measures.length)\n throw new DashboardQueryError('Informe ao menos uma dimensão ou medida')\n\n for (const name of dimensions) {\n if (!model.dimensions[name]) throw new DashboardQueryError(`Dimensão desconhecida: \"${name}\"`)\n }\n\n const filters = compileFilters(model, query)\n return compileStar(model, dimensions, measures, filters, options)\n}\n\nconst range = (count: number) => Array.from({ length: count }, (_, index) => index + 1).join(', ')\n\n/**\n * Cada tabela de origem (a das medidas pedidas) vira uma agregação no próprio\n * grão, com LEFT JOIN só nas tabelas que levam às dimensões e filtros usados:\n *\n * SELECT <dimensões>, SUM(<medida>) FROM fPesagem\n * LEFT JOIN dOperacao ON dOperacao.id = fPesagem.navio_operacao_id\n * WHERE <filtros> GROUP BY <dimensões>\n *\n * Com medidas de mais de uma fato, cada uma agrega separada e o resultado é somado por dimensão\n * (`UNION ALL` + `GROUP BY`) — juntar duas fatos antes de somar multiplicaria os valores. Razões e\n * fórmulas são calculadas no fim, sobre os totais.\n *\n * Filtro numa dimensão que a fato não alcança é ignorado por ela (como no Power BI, a medida não é\n * filtrada); agrupar por uma dimensão que ela não alcança é erro.\n */\nfunction compileStar(\n model: ModelDef,\n dimensions: string[],\n measures: string[],\n filters: Filter[],\n { sampleLimit }: CompileOptions\n): CompiledQuery {\n const { tables } = model\n const tableNames = Object.keys(tables)\n const edges = modelEdges(model)\n const tableOf = (sql: string) => referencedTables(sql, tableNames)\n const dimensionTable = (name: string) => tableOf(model.dimensions[name].sql)[0]\n\n // Agregações agrupadas pela tabela onde agregam\n const groups = new Map<string, string[]>()\n const leafOf = (name: string) => model.measures[name] as AggregateMeasure\n for (const leaf of leafMeasures(model, measures)) {\n const measure = model.measures[leaf]\n if (!isAggregate(measure)) continue\n const home = measureHome(tableOf(measure.sql), edges)\n if (!home) throw new DashboardQueryError(`Medida \"${leaf}\" sem tabela de origem`)\n groups.set(home, [...(groups.get(home) ?? []), leaf])\n }\n if (!groups.size) groups.set(dimensionHome(model, dimensions, filters, edges), [])\n\n const source = (name: string) => {\n const sql = tables[name].sql\n return sampleLimit === undefined\n ? `(${sql})`\n : `(SELECT * FROM (${sql}) t LIMIT ${Math.max(0, Math.trunc(sampleLimit))})`\n }\n\n const aggregate = (home: string, leaves: string[]) => {\n const { paths } = reachFrom(home, edges)\n for (const name of dimensions)\n if (!paths.has(dimensionTable(name)))\n throw new DashboardQueryError(\n leaves.length\n ? `A dimensão \"${name}\" (${dimensionTable(name)}) não se relaciona com ${home}, de onde vêm ${leaves.join(', ')}`\n : `As dimensões ${dimensions.join(', ')} não se relacionam entre si`\n )\n const applied = filters.filter((filter) => paths.has(dimensionTable(filter.dimension)))\n const needed = new Set([\n ...dimensions.map(dimensionTable),\n ...applied.map((filter) => dimensionTable(filter.dimension)),\n ...leaves.flatMap((leaf) => tableOf(leafOf(leaf).sql))\n ])\n const joins = joinPlan(home, needed, edges).map(\n ({ table, edge }) =>\n `${edge.inner ? 'JOIN' : 'LEFT JOIN'} ${source(table)} ${quote(table)}\\n ON ${quote(table)}.${quote(edge.to.column)} = ${quote(edge.from.table)}.${quote(edge.from.column)}`\n )\n const select = [\n ...dimensions.map((name) => `${model.dimensions[name].sql} AS ${quote(name)}`),\n ...leaves.map((leaf) => `${aggregateSql(leafOf(leaf))} AS ${quote(leaf)}`)\n ]\n const sql = [\n `SELECT ${select.join(',\\n ')}`,\n `FROM ${source(home)} ${quote(home)}`,\n ...joins,\n applied.length ? `WHERE ${applied.map((filter) => filter.clause).join(' AND ')}` : '',\n dimensions.length ? `GROUP BY ${range(dimensions.length)}` : ''\n ]\n .filter(Boolean)\n .join('\\n')\n return { home, sql, params: applied.flatMap((filter) => filter.params) }\n }\n\n const aggregates = [...groups].map(([home, leaves]) => aggregate(home, leaves))\n const parts = aggregates.map(({ home, sql, params }) => ({\n name: `fato:${home}`,\n sql: `SELECT COUNT(*) AS n FROM (${sql}) g`,\n params\n }))\n const orderBy = dimensions.length ? `\\nORDER BY ${range(dimensions.length)}` : ''\n\n // Uma tabela só: as medidas saem direto da agregação\n if (aggregates.length === 1) {\n const [only] = aggregates\n if (!measures.length || measures.every((name) => isAggregate(model.measures[name])))\n return { sql: `${only.sql}${orderBy}`, params: only.params, parts }\n }\n\n // Várias (ou razões/fórmulas): junta as colunas já agregadas e calcula as medidas por cima. Cada\n // fato tem uma linha por grupo e as colunas das outras vêm NULL, então o SUM de fora só pega o\n // valor da própria fato — vale para qualquer agregação (média, contagem distinta...), sem reagregar\n const columns = [...dimensions, ...[...groups.values()].flat()]\n const union = aggregates\n .map(({ home }, index) => {\n const own = new Set(groups.get(home))\n const select = columns.map((column) =>\n dimensions.includes(column) || own.has(column) ? quote(column) : `NULL AS ${quote(column)}`\n )\n return `SELECT ${select.join(', ')} FROM ${quote(`__fato${index}`)}`\n })\n .join('\\n UNION ALL ')\n const leaf = (name: string) => `SUM(${quote('__u')}.${quote(name)})`\n const select = [\n ...dimensions.map((name) => `${quote('__u')}.${quote(name)} AS ${quote(name)}`),\n ...measures.map((name) => `${measureSql(model, name, leaf)} AS ${quote(name)}`)\n ]\n const sql = [\n `WITH ${aggregates.map(({ sql }, index) => `${quote(`__fato${index}`)} AS (\\n${sql}\\n)`).join(',\\n')}`,\n `SELECT ${select.join(',\\n ')}`,\n `FROM (\\n ${union}\\n) ${quote('__u')}`,\n dimensions.length ? `GROUP BY ${range(dimensions.length)}${orderBy}` : ''\n ]\n .filter(Boolean)\n .join('\\n')\n return { sql, params: aggregates.flatMap((item) => item.params), parts }\n}\n\n/**\n * Consulta só de dimensões (ex.: opções de um filtro): parte da tabela que alcança todas as\n * dimensões pedidas e o maior número de filtros ativos — com \"Mês = Janeiro\", a lista de navios sai\n * da fato que liga navio e calendário, e só mostra quem tem lançamento em janeiro. Sem filtro, a\n * própria tabela da dimensão (lista tudo, mesmo sem lançamento).\n */\nfunction dimensionHome(\n model: ModelDef,\n dimensions: string[],\n filters: Filter[],\n edges: Edge[]\n): string {\n const tableNames = Object.keys(model.tables)\n const tableOf = (name: string) => referencedTables(model.dimensions[name].sql, tableNames)[0]\n const own = tableOf(dimensions[0])\n const targets = dimensions.map(tableOf)\n const filterTables = filters.map((filter) => tableOf(filter.dimension))\n let best: { table: string; score: number } | null = null\n // A tabela da própria dimensão primeiro: em empate, ela ganha\n for (const table of [own, ...tableNames.filter((name) => name !== own)]) {\n const { paths } = reachFrom(table, edges)\n if (!targets.every((target) => paths.has(target))) continue\n const score = filterTables.filter((target) => paths.has(target)).length\n if (!best || score > best.score) best = { table, score }\n }\n return best?.table ?? own\n}\n","/**\n * Modelo semântico de um dashboard: o equivalente ao modelo do Power BI (tabelas, relacionamentos e\n * medidas DAX), só que versionado em código.\n *\n * - `tables`: tabelas fato (lançamentos, lado N) e dimensão (cadastros, lado 1). O SQL é a consulta\n * de origem — tabela, view ou SELECT, sem agregar.\n * - `relationships`: N:1 (`from: 'fato.coluna'`, `to: 'dimensao.coluna'`); o filtro vai do lado 1\n * para o lado N. Cada medida agrega no grão da própria tabela, então juntar fatos não duplica\n * valores (o problema de \"fan-out\").\n * - `dimensions`: por onde se agrupa e filtra, citando `tabela.coluna` de uma tabela.\n * - `measures`: agregações (`sum`, `count`, `countDistinct`, `avg`, `min`, `max`) sobre\n * `tabela.coluna`, razões entre medidas (`ratio`) ou fórmulas (`formula`).\n *\n * Dialeto: MySQL/MariaDB.\n */\n\nimport {\n type AggregateMeasure,\n type Aggregation,\n type FormulaMeasure,\n type MeasureMeta,\n type ModelDef,\n type RatioMeasure,\n type SumMeasure,\n validateModel\n} from '../model'\n\nexport type {\n AggregateMeasure,\n Aggregation,\n DimensionDef,\n FieldMeta,\n FormulaMeasure,\n MeasureDef,\n MeasureMeta,\n ModelDef,\n RatioMeasure,\n RelationshipDef,\n SumMeasure,\n TableDef\n} from '../model'\n\nexport const sum = (sql: string, meta?: MeasureMeta): SumMeasure => ({ ...meta, type: 'sum', sql })\n\nconst aggregate =\n <T extends Aggregation>(type: T) =>\n (sql: string, meta?: MeasureMeta): AggregateMeasure & { type: T } => ({ ...meta, type, sql })\n\n/** `COUNT(sql)`: valores não nulos. Para contar linhas, use uma coluna que nunca é nula (a chave). */\nexport const count = aggregate('count')\n/** `COUNT(DISTINCT sql)`: ex. quantas operações têm lançamento (`fVeiculo.navio_operacao_id`). */\nexport const countDistinct = aggregate('countDistinct')\n/** `AVG(sql)`: média das linhas (no estrela, das linhas da própria tabela, não de totais). */\nexport const avg = aggregate('avg')\nexport const min = aggregate('min')\nexport const max = aggregate('max')\n\n/** `numerator / denominator`, ambos nomes de medidas do mesmo modelo (nulo quando o divisor é 0). */\nexport const ratio = (\n numerator: string,\n denominator: string,\n meta?: MeasureMeta\n): RatioMeasure => ({\n ...meta,\n type: 'ratio',\n numerator,\n denominator\n})\n\n/**\n * Combina medidas já agregadas: `{nome}` vira o valor da medida. Ex.:\n * `formula('COALESCE({custoMmo}, 0) + COALESCE({custoAppa}, 0)')` — o jeito de somar medidas de\n * fatos diferentes no modelo estrela.\n */\nexport const formula = (expression: string, meta?: MeasureMeta): FormulaMeasure => ({\n ...meta,\n type: 'formula',\n expression\n})\n\n/** Valida o modelo na carga do módulo: erro de definição aparece no deploy, não na primeira query. */\nexport function defineModel<const M extends ModelDef>(model: M): M {\n const errors = validateModel(model)\n if (errors.length) throw new Error(`[${model.name}] ${errors.join('; ')}`)\n return model\n}\n","import { modelImports, modelObjectCode } from '../codegen'\nimport { AGGREGATIONS, type ModelDef } from '../model'\n\n/**\n * Troca, dentro do arquivo do modelo, só o objeto passado ao `defineModel(...)` e o import do\n * `@bluemarble/bi/server`. O resto do arquivo (JSDoc do modelo, outros exports) fica como está.\n */\n\nexport class ModelFileError extends Error {}\n\n/** Índice do `)` que fecha o `(` em `open`, pulando strings, templates e comentários. */\nfunction findClosingParen(source: string, open: number) {\n let depth = 0\n let comments = false\n // Pilha de contexto: '(' / '{' de código, '`' de template, '${' de interpolação\n const stack: string[] = []\n for (let i = open; i < source.length; i++) {\n const char = source[i]\n const next = source[i + 1]\n const inTemplate = stack[stack.length - 1] === '`'\n\n if (inTemplate) {\n if (char === '\\\\') i++\n else if (char === '`') stack.pop()\n else if (char === '$' && next === '{') {\n stack.push('${')\n i++\n }\n continue\n }\n if (char === \"'\" || char === '\"') {\n for (i++; i < source.length && source[i] !== char; i++) if (source[i] === '\\\\') i++\n continue\n }\n if (char === '/' && next === '/') {\n comments = true\n while (i < source.length && source[i] !== '\\n') i++\n continue\n }\n if (char === '/' && next === '*') {\n comments = true\n i = source.indexOf('*/', i + 2) + 1\n if (i === 0) break\n continue\n }\n if (char === '`') stack.push('`')\n else if (char === '{') stack.push('{')\n else if (char === '}')\n stack.pop() // fecha '{' ou '${'\n else if (char === '(') depth++\n else if (char === ')' && --depth === 0) return { close: i, comments }\n }\n throw new ModelFileError('Não encontrei o fim da chamada `defineModel(...)`')\n}\n\nexport function replaceModelInFile(source: string, model: ModelDef) {\n const warnings: string[] = []\n const call = new RegExp(`export\\\\s+const\\\\s+${model.name}\\\\s*=\\\\s*defineModel(?:<[^>]*>)?\\\\s*\\\\(`)\n const match = call.exec(source)\n if (!match)\n throw new ModelFileError(`O arquivo não tem \\`export const ${model.name} = defineModel(...)\\``)\n\n const open = match.index + match[0].length - 1\n const { close, comments } = findClosingParen(source, open)\n if (comments)\n warnings.push('Comentários dentro do defineModel(...) serão perdidos (os de fora ficam).')\n\n let content = `${source.slice(0, open + 1)}${modelObjectCode(model)}${source.slice(close)}`\n\n // Import: mantém o que mais vier do package e acerta os helpers de medida conforme o uso\n const importRegex = /import\\s*\\{([^}]*)\\}\\s*from\\s*'@bluemarble\\/bi\\/server'/\n const current = importRegex.exec(content)\n const helpers = new Set(['defineModel', 'formula', 'ratio', ...AGGREGATIONS])\n const others = (current?.[1] ?? '')\n .split(',')\n .map((name) => name.trim())\n .filter((name) => name && !helpers.has(name))\n const names = [...others, ...modelImports(model)].sort((a, b) =>\n a.localeCompare(b, 'en', { sensitivity: 'base' })\n )\n const line = `import { ${names.join(', ')} } from '@bluemarble/bi/server'`\n content = current ? content.replace(importRegex, line) : `${line}\\n${content}`\n\n const outside = source.slice(0, match.index) + source.slice(close + 1)\n const measureHelpers = [...helpers].filter((name) => name !== 'defineModel')\n if (new RegExp(`\\\\b(${measureHelpers.join('|')})\\\\(`).test(outside))\n warnings.push('O arquivo usa os helpers de medida fora do modelo; confira o import.')\n\n return { content, warnings }\n}\n","import type { DashboardRow } from '../types'\n\n/** Decimal do Prisma, bigint e afins viram number; datas viram ISO. */\nexport function normalizeValue(value: unknown): string | number | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string' || typeof value === 'number') return value\n if (typeof value === 'bigint') return Number(value)\n if (value instanceof Date) return value.toISOString()\n const number = Number(value)\n return Number.isNaN(number) ? String(value) : number\n}\n\nexport function normalizeRow(row: Record<string, unknown>): DashboardRow {\n const normalized: DashboardRow = {}\n for (const [key, value] of Object.entries(row)) normalized[key] = normalizeValue(value)\n return normalized\n}\n","import {\n type ChartsLayout,\n type ChartTypeSpec,\n layoutFileContent,\n layoutModelWarnings,\n resolveChartTypes,\n validateLayout\n} from '../layout'\nimport { isAggregate, modelMeta, validateModel } from '../model'\nimport { parseColumnRef, referencedTables } from '../star'\nimport type {\n DashboardDevtoolsRequest,\n DashboardQuery,\n DashboardResult,\n DashboardRow,\n DashboardSnapshot,\n ModelColumns,\n ModelValidation,\n ProfileStep,\n SchemaTable\n} from '../types'\nimport { compileQuery, DashboardQueryError } from './compile'\nimport type { ModelDef } from './defineModel'\nimport { ModelFileError, replaceModelInFile } from './modelFile'\nimport { normalizeValue } from './normalize'\n\n/**\n * Pedidos do devtools (`query.devtools`) que não são uma consulta comum. Só chega aqui com\n * `runQuery(..., { devtools })` ligado — tudo isso executa SQL vindo do browser ou escreve em disco.\n */\n\nexport type DevtoolsServerOptions = {\n /**\n * Pasta dos arquivos de modelo (`<pasta>/<nome>.ts`, caminho absoluto). Habilita \"Salvar no\n * arquivo\"; só arquivos que já existem são reescritos.\n */\n modelsDir?: string\n /** Pasta dos snapshots de regressão (`<pasta>/<modelo>/<nome>.json`, caminho absoluto). */\n snapshotsDir?: string\n /**\n * Pasta dos layouts de gráficos (`<pasta>/<id>.json`, caminho absoluto). Habilita \"Salvar no\n * arquivo\" na aba Layout; só arquivos que já existem são reescritos.\n */\n layoutsDir?: string\n /**\n * Tipos de gráfico do app (os mesmos `ChartTypeSpec` do `<DashboardProvider charts>`, sem o\n * componente). Sem eles, salvar um layout com esses tipos é recusado.\n */\n chartTypes?: Record<string, ChartTypeSpec>\n /**\n * Formata o arquivo do modelo antes do preview/gravação (ex.: Biome), para o diff mostrar só o\n * que mudou de fato. Sem isso, o arquivo sai com a formatação do gerador.\n */\n format?: (code: string, path: string) => Promise<string>\n}\n\ntype Executor = (sql: string, params: (string | number)[]) => Promise<Record<string, unknown>[]>\n\ntype Context = {\n registered: ModelDef\n /** Rascunho, se houver; senão o registrado. */\n model: ModelDef\n query: DashboardQuery\n request: DashboardDevtoolsRequest\n execute: Executor\n normalize: (row: Record<string, unknown>) => DashboardRow\n options: DevtoolsServerOptions\n /** Executa a consulta normal (com cache) e devolve as linhas. */\n runRows: (model: ModelDef) => Promise<DashboardRow[]>\n}\n\nconst fs = () => import('node:fs/promises')\n\n/** Mensagem do banco sem o embrulho do Prisma (`Raw query failed. Code: ... Message: ...`). */\nexport function dbMessage(error: unknown) {\n const err = error as { meta?: { message?: string }; message?: string }\n const message = err?.meta?.message ?? err?.message ?? String(error)\n return message.match(/Message: `?([\\s\\S]*?)`?$/)?.[1] ?? message\n}\n\nconst timed = async <T>(load: () => Promise<T>) => {\n const startedAt = performance.now()\n const result = await load()\n return { result, durationMs: Math.round(performance.now() - startedAt) }\n}\n\nconst SLUG = /^[A-Za-z0-9_-]{1,80}$/\n\n/** `<pasta>/<arquivo>`, aceitando pasta com `/` ou `\\` no fim (`path.join` no Windows). */\nconst inDir = (dir: string, file: string) => `${dir.replace(/[\\\\/]+$/, '')}/${file}`\n\n/**\n * Conteúdo final do arquivo: formatado pelo app (se der erro, sai sem formatar) e com o fim de\n * linha do arquivo atual — no Windows o checkout costuma vir com CRLF, e o gerador/formatador\n * devolvem LF, o que faria o diff marcar o arquivo inteiro.\n */\nasync function fileContent(\n raw: string,\n path: string,\n previous: string,\n options: DevtoolsServerOptions\n) {\n const { format } = options\n const formatted = format\n ? await Promise.resolve()\n .then(() => format(raw, path))\n .catch((error) => {\n console.warn('[@bluemarble/bi] format falhou; o arquivo sai sem formatar:', error)\n return raw\n })\n : raw\n const content = formatted.replace(/\\r\\n/g, '\\n')\n return previous.includes('\\r\\n') ? content.replace(/\\n/g, '\\r\\n') : content\n}\n\nexport async function handleDevtoolsRequest(context: Context): Promise<DashboardResult | null> {\n const { request } = context\n if (request.describe) return describe(context)\n if (request.schema) return { rows: [], devtools: { schema: await loadSchema(context.execute) } }\n if (request.columns) {\n const { columns, errors } = await describeColumns(context)\n return { rows: [], devtools: { columns, columnErrors: errors } }\n }\n if (request.validate) return { rows: [], devtools: { validation: await validateSql(context) } }\n if (request.save) return { rows: [], devtools: { save: await saveModel(context) } }\n if (request.saveLayout) return { rows: [], devtools: { save: await saveLayout(context) } }\n if (request.snapshots) return { rows: [], devtools: { snapshots: await listSnapshots(context) } }\n if (request.snapshot) return { rows: [], devtools: { snapshot: await saveSnapshot(context) } }\n if (request.compile) {\n const { sql, params } = compileQuery(context.model, context.query)\n const draft = context.model !== context.registered\n return { rows: [], devtools: { debug: { sql, params, durationMs: 0, cached: false, draft } } }\n }\n if (request.explain) {\n const { sql, params } = compileQuery(context.model, context.query)\n const rows = await context.execute(`EXPLAIN ${sql}`, params)\n return { rows: [], devtools: { explain: rows.map(context.normalize) } }\n }\n if (request.profile) return { rows: [], devtools: { profile: await profile(context) } }\n return null\n}\n\nfunction describe({ registered, options }: Context): DashboardResult {\n return {\n rows: [],\n meta: modelMeta(registered),\n devtools: {\n model: registered,\n capabilities: {\n saveModel: !!options.modelsDir,\n snapshots: !!options.snapshotsDir,\n saveLayout: !!options.layoutsDir,\n chartTypes: Object.keys(options.chartTypes ?? {})\n }\n }\n }\n}\n\nasync function profile({ model, query, execute }: Context): Promise<ProfileStep[]> {\n const { sql, params, parts } = compileQuery(model, query)\n // Em sequência: medições em paralelo disputariam o banco e os tempos não diriam nada\n const steps: ProfileStep[] = []\n for (const part of parts) {\n const { result, durationMs } = await timed(() => execute(part.sql, part.params))\n steps.push({ name: part.name, durationMs, rows: Number(result[0]?.n ?? 0) })\n }\n const total = await timed(() => execute(sql, params))\n steps.push({ name: 'consulta', durationMs: total.durationMs, rows: total.result.length })\n return steps\n}\n\nasync function loadSchema(execute: Executor): Promise<SchemaTable[]> {\n const rows = await execute(\n `SELECT c.TABLE_NAME AS tableName, t.TABLE_TYPE AS tableType,\n c.COLUMN_NAME AS columnName, c.DATA_TYPE AS dataType\n FROM information_schema.COLUMNS c\n JOIN information_schema.TABLES t\n ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME\n WHERE c.TABLE_SCHEMA = DATABASE()\n ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`,\n []\n )\n const tables = new Map<string, SchemaTable>()\n for (const row of rows) {\n const name = String(row.tableName)\n let table = tables.get(name)\n if (!table) {\n table = { name, type: String(row.tableType).includes('VIEW') ? 'view' : 'table', columns: [] }\n tables.set(name, table)\n }\n table.columns.push({ name: String(row.columnName), type: String(row.dataType) })\n }\n return [...tables.values()]\n}\n\n/** Poucas chaves bastam para o banco acusar erro de SQL e para descobrir as colunas. */\nconst SAMPLE_KEYS = 20\n\nconst emptyColumns = (): ModelColumns => ({ tables: {}, numeric: [] })\n\n/** Colunas numéricas da primeira linha (Decimal do Prisma e bigint contam). */\nfunction numericColumns(node: string, row: Record<string, unknown> | undefined) {\n return Object.entries(row ?? {})\n .filter(([, value]) => typeof normalizeValue(value) === 'number')\n .map(([column]) => `${node}.${column}`)\n}\n\n/**\n * Colunas de cada tabela do modelo (uma linha de cada). `errors` traz o que o banco recusou, com as\n * chaves da validação (`table:<nome>`).\n */\nasync function loadColumns(model: ModelDef, execute: Executor) {\n const columns = emptyColumns()\n const errors: Record<string, string> = {}\n const attempt = async (key: string, sql: string) => {\n try {\n return await execute(sql, [])\n } catch (error) {\n errors[key] = dbMessage(error)\n return null\n }\n }\n for (const [name, table] of Object.entries(model.tables)) {\n const rows = await attempt(`table:${name}`, `SELECT * FROM (${table.sql}) t LIMIT 1`)\n if (!rows) continue\n columns.tables[name] = Object.keys(rows[0] ?? {})\n columns.numeric.push(...numericColumns(name, rows[0]))\n }\n return { columns, errors }\n}\n\nasync function describeColumns({ model, execute }: Context) {\n const errors = validateModel(model)\n if (errors.length) throw new DashboardQueryError(`Modelo inválido: ${errors.join('; ')}`)\n return loadColumns(model, execute)\n}\n\nasync function validateSql({ model, execute }: Context): Promise<ModelValidation> {\n const validation: ModelValidation = { errors: {}, warnings: {}, columns: emptyColumns() }\n const modelErrors = validateModel(model)\n if (modelErrors.length) {\n validation.errors.model = modelErrors.join('; ')\n return validation\n }\n\n const attempt = async (key: string, sql: string) => {\n try {\n return await execute(sql, [])\n } catch (error) {\n validation.errors[key] = dbMessage(error)\n return null\n }\n }\n\n const loaded = await loadColumns(model, execute)\n validation.columns = loaded.columns\n Object.assign(validation.errors, loaded.errors)\n const tableNames = Object.keys(model.tables)\n\n for (const [index, relationship] of (model.relationships ?? []).entries()) {\n const key = `relationship:${index}`\n const from = parseColumnRef(relationship.from)\n const to = parseColumnRef(relationship.to)\n if (!from || !to) continue\n const missing = [from, to].find(\n (ref) =>\n validation.columns.tables[ref.table]?.length &&\n !validation.columns.tables[ref.table].includes(ref.column)\n )\n if (missing) {\n validation.errors[key] = `a tabela \"${missing.table}\" não tem a coluna \"${missing.column}\"`\n continue\n }\n // O lado 1 precisa ser único, senão a fato multiplica linhas ao juntar\n const toSql = model.tables[to.table]?.sql\n if (!toSql || validation.errors[`table:${to.table}`]) continue\n const repeated = await attempt(\n key,\n `SELECT \\`${to.column}\\` AS v FROM (${toSql}) t WHERE \\`${to.column}\\` IS NOT NULL GROUP BY 1 HAVING COUNT(*) > 1 LIMIT 1`\n )\n if (repeated?.length)\n validation.errors[key] =\n `o lado 1 (${relationship.to}) tem valor repetido: ${String(normalizeValue(repeated[0].v))}`\n }\n\n for (const [name, dimension] of Object.entries(model.dimensions)) {\n const table = referencedTables(dimension.sql, tableNames)[0]\n if (validation.errors[`table:${table}`]) continue\n const sql = `SELECT ${dimension.sql} AS v FROM (SELECT * FROM (${model.tables[table]?.sql}) t LIMIT ${SAMPLE_KEYS}) \\`${table}\\` WHERE ${dimension.sql} IS NOT NULL LIMIT 1`\n const rows = await attempt(`dimension:${name}`, sql)\n const value = rows?.[0]?.v\n if (value === undefined || value === null) continue\n const isNumber = typeof value === 'number' || typeof value === 'bigint'\n if (dimension.type === 'number' && !isNumber && Number.isNaN(Number(value)))\n validation.warnings[`dimension:${name}`] = `tipo number, mas o banco devolveu \"${value}\"`\n if (dimension.type === 'string' && isNumber)\n validation.warnings[`dimension:${name}`] =\n 'tipo string, mas o banco devolveu número (filtros vão chegar como texto)'\n }\n\n for (const [name, measure] of Object.entries(model.measures)) {\n if (!isAggregate(measure)) continue // razão e fórmula só combinam outras medidas\n const failed = referencedTables(measure.sql, tableNames).some(\n (table) => validation.errors[`table:${table}`]\n )\n if (failed) continue\n const { sql, params } = compileQuery(\n model,\n { model: model.name, measures: [name] },\n { sampleLimit: SAMPLE_KEYS }\n )\n try {\n await execute(sql, params)\n } catch (error) {\n validation.errors[`measure:${name}`] = dbMessage(error)\n }\n }\n\n return validation\n}\n\nasync function saveModel({ registered, model, request, options }: Context) {\n if (!options.modelsDir) throw new DashboardQueryError('O endpoint não configurou `modelsDir`')\n if (!request.model) throw new DashboardQueryError('Nada para salvar: não há rascunho')\n if (model.name !== registered.name)\n throw new DashboardQueryError('O rascunho não pode trocar o nome do modelo')\n\n const path = inDir(options.modelsDir, `${registered.name}.ts`)\n const { readFile, writeFile } = await fs()\n let previous: string\n try {\n previous = await readFile(path, 'utf8')\n } catch {\n throw new DashboardQueryError(`Arquivo do modelo não encontrado: ${path}`)\n }\n\n try {\n const replaced = replaceModelInFile(previous, model)\n const { warnings } = replaced\n const content = await fileContent(replaced.content, path, previous, options)\n const written = request.save === 'write' && content !== previous\n if (written) await writeFile(path, content, 'utf8')\n return { path, previous, content, written, warnings }\n } catch (error) {\n if (error instanceof ModelFileError) throw new DashboardQueryError(error.message)\n throw error\n }\n}\n\nasync function saveLayout({ model, request, options }: Context) {\n if (!options.layoutsDir) throw new DashboardQueryError('O endpoint não configurou `layoutsDir`')\n const { layout, mode } = request.saveLayout ?? ({} as { layout?: ChartsLayout; mode?: string })\n const types = resolveChartTypes(options.chartTypes)\n const errors = layout ? validateLayout(layout, types) : ['Nada para salvar: não há layout']\n if (errors.length) throw new DashboardQueryError(`Layout inválido: ${errors.join('; ')}`)\n const valid = layout as ChartsLayout\n\n const path = inDir(options.layoutsDir, `${valid.id}.json`)\n const { readFile, writeFile } = await fs()\n let previous: string\n try {\n previous = await readFile(path, 'utf8')\n } catch {\n throw new DashboardQueryError(`Arquivo do layout não encontrado: ${path}`)\n }\n\n const content = await fileContent(layoutFileContent(valid), path, previous, options)\n const written = mode === 'write' && content !== previous\n if (written) await writeFile(path, content, 'utf8')\n // Contra o modelo em vigor na página (o rascunho, se houver)\n return { path, previous, content, written, warnings: layoutModelWarnings(valid, model, types) }\n}\n\nconst snapshotDir = (options: DevtoolsServerOptions, model: string) => {\n if (!options.snapshotsDir)\n throw new DashboardQueryError('O endpoint não configurou `snapshotsDir`')\n return inDir(options.snapshotsDir, model)\n}\n\nasync function saveSnapshot({ registered, query, request, options, runRows }: Context) {\n const { name, overwrite } = request.snapshot ?? { name: '' }\n if (!SLUG.test(name))\n throw new DashboardQueryError('Nome do snapshot: letras, números, \"-\" e \"_\" (até 80)')\n const dir = snapshotDir(options, registered.name)\n const path = `${dir}/${name}.json`\n const { mkdir, readFile, writeFile } = await fs()\n\n if (!overwrite) {\n const exists = await readFile(path, 'utf8').then(\n () => true,\n () => false\n )\n if (exists) throw new DashboardQueryError(`Já existe um snapshot \"${name}\"`)\n }\n\n // Sempre o modelo registrado: o snapshot é a referência que o rascunho tem que bater\n const snapshot: DashboardSnapshot = {\n name,\n createdAt: new Date().toISOString(),\n query: {\n model: registered.name,\n dimensions: query.dimensions ?? [],\n measures: query.measures ?? [],\n filters: query.filters ?? {}\n },\n rows: await runRows(registered)\n }\n await mkdir(dir, { recursive: true })\n await writeFile(path, `${JSON.stringify(snapshot, null, 2)}\\n`, 'utf8')\n return { path }\n}\n\nasync function listSnapshots({ registered, options }: Context): Promise<DashboardSnapshot[]> {\n const dir = snapshotDir(options, registered.name)\n const { readdir, readFile } = await fs()\n const files = await readdir(dir).catch(() => [] as string[])\n const snapshots = await Promise.all(\n files\n .filter((file) => file.endsWith('.json'))\n .sort()\n .map(\n async (file) => JSON.parse(await readFile(`${dir}/${file}`, 'utf8')) as DashboardSnapshot\n )\n )\n return snapshots\n}\n","import { modelMeta, validateModel } from '../model'\nimport type { DashboardQuery, DashboardResult, DashboardRow } from '../types'\nimport { compileQuery, DashboardQueryError } from './compile'\nimport type { ModelDef } from './defineModel'\nimport { type DevtoolsServerOptions, dbMessage, handleDevtoolsRequest } from './devtools'\nimport { normalizeRow } from './normalize'\n\n/** Executa o SQL no banco do app (ex.: `(sql, params) => prisma.$queryRawUnsafe(sql, ...params)`). */\nexport type QueryExecutor = (sql: string, params: (string | number)[]) => Promise<unknown[]>\n\nexport type RunQueryOptions = {\n /**\n * Por quanto tempo reaproveitar o resultado de uma mesma consulta (mesmo SQL + parâmetros).\n * Faz o papel do \"refresh\" do Power BI: alternar filtros não recalcula tudo de novo. 0 = sem cache.\n */\n cacheTtlMs?: number\n /**\n * Aceita os pedidos do devtools (`query.devtools`): debug, rascunho de modelo, EXPLAIN, validação\n * de SQL etc. O rascunho é SQL vindo do browser — ligue SÓ em desenvolvimento. Passe as pastas\n * (`modelsDir`, `snapshotsDir`) para habilitar salvar o modelo e os snapshots.\n */\n devtools?: boolean | DevtoolsServerOptions\n}\n\ntype Execution = { rows: DashboardRow[]; durationMs: number }\n\nconst MAX_CACHE_ENTRIES = 500\nconst cache = new Map<string, { expiresAt: number; execution: Promise<Execution> }>()\n\nfunction fromCache(key: string, ttl: number, load: () => Promise<Execution>) {\n const now = Date.now()\n const hit = cache.get(key)\n if (hit && hit.expiresAt > now) return { execution: hit.execution, cached: true }\n\n if (cache.size >= MAX_CACHE_ENTRIES) {\n for (const [k, entry] of cache) if (entry.expiresAt <= now) cache.delete(k)\n if (cache.size >= MAX_CACHE_ENTRIES) cache.delete(cache.keys().next().value as string)\n }\n // Guarda a promise: requests simultâneas da mesma consulta esperam a mesma execução\n const execution = load()\n cache.set(key, { expiresAt: now + ttl, execution })\n execution.catch(() => cache.delete(key))\n return { execution, cached: false }\n}\n\nexport async function runQuery(\n models: Record<string, ModelDef>,\n query: DashboardQuery,\n execute: QueryExecutor,\n options: RunQueryOptions = {}\n): Promise<DashboardResult> {\n // `debug` desligado é só ignorado (a página continua funcionando com um endpoint sem devtools);\n // o resto expõe ou altera o modelo e é recusado\n const { debug, ...privileged } = query?.devtools ?? {}\n const devtools = options.devtools ? query?.devtools : undefined\n if (!options.devtools && Object.values(privileged).some(Boolean))\n throw new DashboardQueryError('Devtools desabilitado neste ambiente')\n\n const registered = models[query?.model]\n if (!registered) throw new DashboardQueryError(`Modelo desconhecido: \"${query?.model}\"`)\n\n const draft = devtools?.model\n if (draft) {\n const errors = validateModel(draft)\n if (errors.length) throw new DashboardQueryError(`Rascunho inválido: ${errors.join('; ')}`)\n }\n const model = draft ?? registered\n\n // Rótulos/formatos (sem SQL): liberado em produção, os componentes usam como padrão\n if (query.meta) return { rows: [], meta: modelMeta(model) }\n\n const ttl = options.cacheTtlMs ?? 0\n const run = async (target: ModelDef) => {\n const { sql, params } = compileQuery(target, query)\n const load = async (): Promise<Execution> => {\n const startedAt = performance.now()\n const rows = (await execute(sql, params)) as Record<string, unknown>[]\n return { durationMs: Math.round(performance.now() - startedAt), rows: rows.map(normalizeRow) }\n }\n // O rascunho muda o SQL, então nunca colide com o cache do modelo registrado\n const { execution, cached } =\n ttl > 0\n ? fromCache(JSON.stringify([sql, params]), ttl, load)\n : { execution: load(), cached: false }\n return { sql, params, cached, ...(await execution) }\n }\n\n if (devtools) {\n if (devtools.clearCache) cache.clear()\n try {\n const handled = await handleDevtoolsRequest({\n registered,\n model,\n query,\n request: devtools,\n execute: (sql, params) => execute(sql, params) as Promise<Record<string, unknown>[]>,\n normalize: normalizeRow,\n options: typeof options.devtools === 'object' ? options.devtools : {},\n runRows: async (target) => (await run(target)).rows\n })\n if (handled) return handled\n } catch (error) {\n // Erro de SQL no devtools é do rascunho/consulta, não do servidor: vira 400 com a mensagem do banco\n if (error instanceof DashboardQueryError) throw error\n throw new DashboardQueryError(dbMessage(error))\n }\n }\n\n const { rows, sql, params, durationMs, cached } = await run(model).catch((error) => {\n // Com o devtools, o erro do banco volta como 400 com a mensagem dele (o painel mostra o SQL)\n if (!devtools || error instanceof DashboardQueryError) throw error\n throw new DashboardQueryError(dbMessage(error))\n })\n if (!devtools?.debug) return { rows }\n return { rows, devtools: { debug: { sql, params, durationMs, cached, draft: !!draft } } }\n}\n"],"mappings":";;;AAKA,IAAa,sBAAb,cAAyC,MAAM,CAAC;AAiBhD,MAAM,oBAAoB;AAE1B,MAAM,SAAS,eAAuB,KAAK,WAAW;;;;;AAMtD,SAAS,WACP,OACA,MACA,MACA,QAAkB,CAAC,GACX;CACR,IAAI,MAAM,SAAS,IAAI,GACrB,MAAM,IAAI,oBAAoB,oBAAoB,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,KAAK,MAAM,GAAG;CACnF,MAAM,UAAU,MAAM,SAAS;CAC/B,IAAI,CAAC,SAAS,MAAM,IAAI,oBAAoB,yBAAyB,KAAK,EAAE;CAC5E,IAAI,YAAY,OAAO,GAAG,OAAO,KAAK,MAAM,OAAO;CACnD,MAAM,OAAO,CAAC,GAAG,OAAO,IAAI;CAC5B,MAAM,OAAO,UAAkB,WAAW,OAAO,OAAO,MAAM,IAAI;CAClE,IAAI,QAAQ,SAAS,SACnB,OAAO,IAAI,IAAI,QAAQ,SAAS,EAAE,aAAa,IAAI,QAAQ,WAAW,EAAE;CAC1E,OAAO,IAAI,QAAQ,WAAW,QAAQ,kCAAkC,GAAG,UAAU,IAAI,IAAI,KAAK,EAAE,EAAE,EAAE;AAC1G;;AAGA,SAAS,aAAa,OAAiB,OAA2B;CAChE,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,QAAQ,OACjB,WAAW,OAAO,OAAO,MAAM,YAAY;EACzC,OAAO,IAAI,IAAI;EACf,OAAO,QAAQ;CACjB,CAAC;CACH,OAAO,CAAC,GAAG,MAAM;AACnB;AAIA,SAAS,eAAe,OAAiB,OAAiC;CACxE,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,GAAG;EAChE,MAAM,YAAY,MAAM,WAAW;EACnC,IAAI,CAAC,WAAW,MAAM,IAAI,oBAAoB,qCAAqC,KAAK,EAAE;EAC1F,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,CAAC,OAAO,QAAQ;EAC9C,IAAI,OAAO,SAAS,mBAClB,MAAM,IAAI,oBAAoB,6BAA6B,KAAK,EAAE;EACpE,KAAK,MAAM,SAAS,QAElB,IAAI,EADU,UAAU,SAAS,WAAW,OAAO,SAAS,KAAK,IAAI,OAAO,UAAU,WAC1E,MAAM,IAAI,oBAAoB,6BAA6B,KAAK,KAAK,OAAO;EAE1F,QAAQ,KAAK;GACX,WAAW;GACX,QAAQ,GAAG,UAAU,IAAI,OAAO,OAAO,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;GACjE,QAAQ;EACV,CAAC;CACH;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,aACd,OACA,OACA,UAA0B,CAAC,GACZ;CACf,MAAM,aAAa,MAAM,cAAc,CAAC;CACxC,MAAM,WAAW,MAAM,YAAY,CAAC;CACpC,IAAI,CAAC,WAAW,UAAU,CAAC,SAAS,QAClC,MAAM,IAAI,oBAAoB,yCAAyC;CAEzE,KAAK,MAAM,QAAQ,YACjB,IAAI,CAAC,MAAM,WAAW,OAAO,MAAM,IAAI,oBAAoB,2BAA2B,KAAK,EAAE;CAI/F,OAAO,YAAY,OAAO,YAAY,UADtB,eAAe,OAAO,KACgB,GAAG,OAAO;AAClE;AAEA,MAAM,SAAS,UAAkB,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAAU,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;;;;;;;AAiBjG,SAAS,YACP,OACA,YACA,UACA,SACA,EAAE,eACa;CACf,MAAM,EAAE,WAAW;CACnB,MAAM,aAAa,OAAO,KAAK,MAAM;CACrC,MAAM,QAAQ,WAAW,KAAK;CAC9B,MAAM,WAAW,QAAgB,iBAAiB,KAAK,UAAU;CACjE,MAAM,kBAAkB,SAAiB,QAAQ,MAAM,WAAW,KAAK,CAAC,GAAG,CAAC,CAAC;CAG7E,MAAM,yBAAS,IAAI,IAAsB;CACzC,MAAM,UAAU,SAAiB,MAAM,SAAS;CAChD,KAAK,MAAM,QAAQ,aAAa,OAAO,QAAQ,GAAG;EAChD,MAAM,UAAU,MAAM,SAAS;EAC/B,IAAI,CAAC,YAAY,OAAO,GAAG;EAC3B,MAAM,OAAO,YAAY,QAAQ,QAAQ,GAAG,GAAG,KAAK;EACpD,IAAI,CAAC,MAAM,MAAM,IAAI,oBAAoB,WAAW,KAAK,uBAAuB;EAChF,OAAO,IAAI,MAAM,CAAC,GAAI,OAAO,IAAI,IAAI,KAAK,CAAC,GAAI,IAAI,CAAC;CACtD;CACA,IAAI,CAAC,OAAO,MAAM,OAAO,IAAI,cAAc,OAAO,YAAY,SAAS,KAAK,GAAG,CAAC,CAAC;CAEjF,MAAM,UAAU,SAAiB;EAC/B,MAAM,MAAM,OAAO,KAAK,CAAC;EACzB,OAAO,gBAAgB,KAAA,IACnB,IAAI,IAAI,KACR,mBAAmB,IAAI,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,CAAC,EAAE;CAC9E;CAEA,MAAM,aAAa,MAAc,WAAqB;EACpD,MAAM,EAAE,UAAU,UAAU,MAAM,KAAK;EACvC,KAAK,MAAM,QAAQ,YACjB,IAAI,CAAC,MAAM,IAAI,eAAe,IAAI,CAAC,GACjC,MAAM,IAAI,oBACR,OAAO,SACH,eAAe,KAAK,KAAK,eAAe,IAAI,EAAE,yBAAyB,KAAK,gBAAgB,OAAO,KAAK,IAAI,MAC5G,gBAAgB,WAAW,KAAK,IAAI,EAAE,4BAC5C;EACJ,MAAM,UAAU,QAAQ,QAAQ,WAAW,MAAM,IAAI,eAAe,OAAO,SAAS,CAAC,CAAC;EACtF,MAAM,yBAAS,IAAI,IAAI;GACrB,GAAG,WAAW,IAAI,cAAc;GAChC,GAAG,QAAQ,KAAK,WAAW,eAAe,OAAO,SAAS,CAAC;GAC3D,GAAG,OAAO,SAAS,SAAS,QAAQ,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC;EACvD,CAAC;EACD,MAAM,QAAQ,SAAS,MAAM,QAAQ,KAAK,CAAC,CAAC,KACzC,EAAE,OAAO,WACR,GAAG,KAAK,QAAQ,SAAS,YAAY,GAAG,OAAO,KAAK,EAAE,GAAG,MAAM,KAAK,EAAE,SAAS,MAAM,KAAK,EAAE,GAAG,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,MAAM,KAAK,KAAK,KAAK,EAAE,GAAG,MAAM,KAAK,KAAK,MAAM,GAC9K;EAcA,OAAO;GAAE;GAAM,KATH;IACV,UAAU,CAJV,GAAG,WAAW,KAAK,SAAS,GAAG,MAAM,WAAW,KAAK,CAAC,IAAI,MAAM,MAAM,IAAI,GAAG,GAC7E,GAAG,OAAO,KAAK,SAAS,GAAG,aAAa,OAAO,IAAI,CAAC,EAAE,MAAM,MAAM,IAAI,GAAG,CAG1D,CAAC,CAAC,KAAK,OAAO;IAC7B,QAAQ,OAAO,IAAI,EAAE,GAAG,MAAM,IAAI;IAClC,GAAG;IACH,QAAQ,SAAS,SAAS,QAAQ,KAAK,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,OAAO,MAAM;IACnF,WAAW,SAAS,YAAY,MAAM,WAAW,MAAM,MAAM;GAC/D,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IACS;GAAG,QAAQ,QAAQ,SAAS,WAAW,OAAO,MAAM;EAAE;CACzE;CAEA,MAAM,aAAa,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,UAAU,MAAM,MAAM,CAAC;CAC9E,MAAM,QAAQ,WAAW,KAAK,EAAE,MAAM,KAAK,cAAc;EACvD,MAAM,QAAQ;EACd,KAAK,8BAA8B,IAAI;EACvC;CACF,EAAE;CACF,MAAM,UAAU,WAAW,SAAS,cAAc,MAAM,WAAW,MAAM,MAAM;CAG/E,IAAI,WAAW,WAAW,GAAG;EAC3B,MAAM,CAAC,QAAQ;EACf,IAAI,CAAC,SAAS,UAAU,SAAS,OAAO,SAAS,YAAY,MAAM,SAAS,KAAK,CAAC,GAChF,OAAO;GAAE,KAAK,GAAG,KAAK,MAAM;GAAW,QAAQ,KAAK;GAAQ;EAAM;CACtE;CAKA,MAAM,UAAU,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;CAC9D,MAAM,QAAQ,WACX,KAAK,EAAE,QAAQ,UAAU;EACxB,MAAM,MAAM,IAAI,IAAI,OAAO,IAAI,IAAI,CAAC;EAIpC,OAAO,UAHQ,QAAQ,KAAK,WAC1B,WAAW,SAAS,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,MAAM,MAAM,IAAI,WAAW,MAAM,MAAM,GAEpE,CAAC,CAAC,KAAK,IAAI,EAAE,QAAQ,MAAM,SAAS,OAAO;CACnE,CAAC,CAAC,CACD,KAAK,gBAAgB;CACxB,MAAM,QAAQ,SAAiB,OAAO,MAAM,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE;CAClE,MAAM,SAAS,CACb,GAAG,WAAW,KAAK,SAAS,GAAG,MAAM,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,MAAM,MAAM,IAAI,GAAG,GAC9E,GAAG,SAAS,KAAK,SAAS,GAAG,WAAW,OAAO,MAAM,IAAI,EAAE,MAAM,MAAM,IAAI,GAAG,CAChF;CASA,OAAO;EAAE,KARG;GACV,QAAQ,WAAW,KAAK,EAAE,OAAO,UAAU,GAAG,MAAM,SAAS,OAAO,EAAE,SAAS,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK;GACnG,UAAU,OAAO,KAAK,OAAO;GAC7B,aAAa,MAAM,MAAM,MAAM,KAAK;GACpC,WAAW,SAAS,YAAY,MAAM,WAAW,MAAM,IAAI,YAAY;EACzE,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IACG;EAAG,QAAQ,WAAW,SAAS,SAAS,KAAK,MAAM;EAAG;CAAM;AACzE;;;;;;;AAQA,SAAS,cACP,OACA,YACA,SACA,OACQ;CACR,MAAM,aAAa,OAAO,KAAK,MAAM,MAAM;CAC3C,MAAM,WAAW,SAAiB,iBAAiB,MAAM,WAAW,KAAK,CAAC,KAAK,UAAU,CAAC,CAAC;CAC3F,MAAM,MAAM,QAAQ,WAAW,EAAE;CACjC,MAAM,UAAU,WAAW,IAAI,OAAO;CACtC,MAAM,eAAe,QAAQ,KAAK,WAAW,QAAQ,OAAO,SAAS,CAAC;CACtE,IAAI,OAAgD;CAEpD,KAAK,MAAM,SAAS,CAAC,KAAK,GAAG,WAAW,QAAQ,SAAS,SAAS,GAAG,CAAC,GAAG;EACvE,MAAM,EAAE,UAAU,UAAU,OAAO,KAAK;EACxC,IAAI,CAAC,QAAQ,OAAO,WAAW,MAAM,IAAI,MAAM,CAAC,GAAG;EACnD,MAAM,QAAQ,aAAa,QAAQ,WAAW,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC;EACjE,IAAI,CAAC,QAAQ,QAAQ,KAAK,OAAO,OAAO;GAAE;GAAO;EAAM;CACzD;CACA,OAAO,MAAM,SAAS;AACxB;;;;;;;;;;;;;;;;;;AC1NA,MAAa,OAAO,KAAa,UAAoC;CAAE,GAAG;CAAM,MAAM;CAAO;AAAI;AAEjG,MAAM,aACoB,UACvB,KAAa,UAAwD;CAAE,GAAG;CAAM;CAAM;AAAI;;AAG7F,MAAa,QAAQ,UAAU,OAAO;;AAEtC,MAAa,gBAAgB,UAAU,eAAe;;AAEtD,MAAa,MAAM,UAAU,KAAK;AAClC,MAAa,MAAM,UAAU,KAAK;AAClC,MAAa,MAAM,UAAU,KAAK;;AAGlC,MAAa,SACX,WACA,aACA,UACkB;CAClB,GAAG;CACH,MAAM;CACN;CACA;AACF;;;;;;AAOA,MAAa,WAAW,YAAoB,UAAwC;CAClF,GAAG;CACH,MAAM;CACN;AACF;;AAGA,SAAgB,YAAsC,OAAa;CACjE,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,OAAO,QAAQ,MAAM,IAAI,MAAM,IAAI,MAAM,KAAK,IAAI,OAAO,KAAK,IAAI,GAAG;CACzE,OAAO;AACT;;;;;;;AC7EA,IAAa,iBAAb,cAAoC,MAAM,CAAC;;AAG3C,SAAS,iBAAiB,QAAgB,MAAc;CACtD,IAAI,QAAQ;CACZ,IAAI,WAAW;CAEf,MAAM,QAAkB,CAAC;CACzB,KAAK,IAAI,IAAI,MAAM,IAAI,OAAO,QAAQ,KAAK;EACzC,MAAM,OAAO,OAAO;EACpB,MAAM,OAAO,OAAO,IAAI;EAGxB,IAFmB,MAAM,MAAM,SAAS,OAAO,KAE/B;GACd,IAAI,SAAS,MAAM;QACd,IAAI,SAAS,KAAK,MAAM,IAAI;QAC5B,IAAI,SAAS,OAAO,SAAS,KAAK;IACrC,MAAM,KAAK,IAAI;IACf;GACF;GACA;EACF;EACA,IAAI,SAAS,OAAO,SAAS,MAAK;GAChC,KAAK,KAAK,IAAI,OAAO,UAAU,OAAO,OAAO,MAAM,KAAK,IAAI,OAAO,OAAO,MAAM;GAChF;EACF;EACA,IAAI,SAAS,OAAO,SAAS,KAAK;GAChC,WAAW;GACX,OAAO,IAAI,OAAO,UAAU,OAAO,OAAO,MAAM;GAChD;EACF;EACA,IAAI,SAAS,OAAO,SAAS,KAAK;GAChC,WAAW;GACX,IAAI,OAAO,QAAQ,MAAM,IAAI,CAAC,IAAI;GAClC,IAAI,MAAM,GAAG;GACb;EACF;EACA,IAAI,SAAS,KAAK,MAAM,KAAK,GAAG;OAC3B,IAAI,SAAS,KAAK,MAAM,KAAK,GAAG;OAChC,IAAI,SAAS,KAChB,MAAM,IAAI;OACP,IAAI,SAAS,KAAK;OAClB,IAAI,SAAS,OAAO,EAAE,UAAU,GAAG,OAAO;GAAE,OAAO;GAAG;EAAS;CACtE;CACA,MAAM,IAAI,eAAe,mDAAmD;AAC9E;AAEA,SAAgB,mBAAmB,QAAgB,OAAiB;CAClE,MAAM,WAAqB,CAAC;CAE5B,MAAM,QAAQ,IADG,OAAO,sBAAsB,MAAM,KAAK,wCACxC,CAAC,CAAC,KAAK,MAAM;CAC9B,IAAI,CAAC,OACH,MAAM,IAAI,eAAe,oCAAoC,MAAM,KAAK,sBAAsB;CAEhG,MAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC,SAAS;CAC7C,MAAM,EAAE,OAAO,aAAa,iBAAiB,QAAQ,IAAI;CACzD,IAAI,UACF,SAAS,KAAK,2EAA2E;CAE3F,IAAI,UAAU,GAAG,OAAO,MAAM,GAAG,OAAO,CAAC,IAAI,gBAAgB,KAAK,IAAI,OAAO,MAAM,KAAK;CAGxF,MAAM,cAAc;CACpB,MAAM,UAAU,YAAY,KAAK,OAAO;CACxC,MAAM,0BAAU,IAAI,IAAI;EAAC;EAAe;EAAW;EAAS,GAAG;CAAY,CAAC;CAQ5E,MAAM,OAAO,YAHC,CAAC,IAJC,UAAU,MAAM,GAAA,CAC7B,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QAAQ,SAAS,QAAQ,CAAC,QAAQ,IAAI,IAAI,CACtB,GAAG,GAAG,aAAa,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,MACzD,EAAE,cAAc,GAAG,MAAM,EAAE,aAAa,OAAO,CAAC,CAErB,CAAC,CAAC,KAAK,IAAI,EAAE;CAC1C,UAAU,UAAU,QAAQ,QAAQ,aAAa,IAAI,IAAI,GAAG,KAAK,IAAI;CAErE,MAAM,UAAU,OAAO,MAAM,GAAG,MAAM,KAAK,IAAI,OAAO,MAAM,QAAQ,CAAC;CACrE,MAAM,iBAAiB,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ,SAAS,SAAS,aAAa;CAC3E,IAAI,IAAI,OAAO,OAAO,eAAe,KAAK,GAAG,EAAE,KAAK,CAAC,CAAC,KAAK,OAAO,GAChE,SAAS,KAAK,sEAAsE;CAEtF,OAAO;EAAE;EAAS;CAAS;AAC7B;;;;ACtFA,SAAgB,eAAe,OAAwC;CACrE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO;CACnE,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,KAAK;CAClD,IAAI,iBAAiB,MAAM,OAAO,MAAM,YAAY;CACpD,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,OAAO,MAAM,MAAM,IAAI,OAAO,KAAK,IAAI;AAChD;AAEA,SAAgB,aAAa,KAA4C;CACvE,MAAM,aAA2B,CAAC;CAClC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG,WAAW,OAAO,eAAe,KAAK;CACtF,OAAO;AACT;;;ACuDA,MAAM,WAAW,OAAO;;AAGxB,SAAgB,UAAU,OAAgB;CACxC,MAAM,MAAM;CACZ,MAAM,UAAU,KAAK,MAAM,WAAW,KAAK,WAAW,OAAO,KAAK;CAClE,OAAO,QAAQ,MAAM,0BAA0B,CAAC,GAAG,MAAM;AAC3D;AAEA,MAAM,QAAQ,OAAU,SAA2B;CACjD,MAAM,YAAY,YAAY,IAAI;CAElC,OAAO;EAAE,QAAA,MADY,KAAK;EACT,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS;CAAE;AACzE;AAEA,MAAM,OAAO;;AAGb,MAAM,SAAS,KAAa,SAAiB,GAAG,IAAI,QAAQ,WAAW,EAAE,EAAE,GAAG;;;;;;AAO9E,eAAe,YACb,KACA,MACA,UACA,SACA;CACA,MAAM,EAAE,WAAW;CASnB,MAAM,WARY,SACd,MAAM,QAAQ,QAAQ,CAAC,CACpB,WAAW,OAAO,KAAK,IAAI,CAAC,CAAC,CAC7B,OAAO,UAAU;EAChB,QAAQ,KAAK,+DAA+D,KAAK;EACjF,OAAO;CACT,CAAC,IACH,IAAA,CACsB,QAAQ,SAAS,IAAI;CAC/C,OAAO,SAAS,SAAS,MAAM,IAAI,QAAQ,QAAQ,OAAO,MAAM,IAAI;AACtE;AAEA,eAAsB,sBAAsB,SAAmD;CAC7F,MAAM,EAAE,YAAY;CACpB,IAAI,QAAQ,UAAU,OAAO,SAAS,OAAO;CAC7C,IAAI,QAAQ,QAAQ,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,QAAQ,MAAM,WAAW,QAAQ,OAAO,EAAE;CAAE;CAC/F,IAAI,QAAQ,SAAS;EACnB,MAAM,EAAE,SAAS,WAAW,MAAM,gBAAgB,OAAO;EACzD,OAAO;GAAE,MAAM,CAAC;GAAG,UAAU;IAAE;IAAS,cAAc;GAAO;EAAE;CACjE;CACA,IAAI,QAAQ,UAAU,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,YAAY,MAAM,YAAY,OAAO,EAAE;CAAE;CAC9F,IAAI,QAAQ,MAAM,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,MAAM,MAAM,UAAU,OAAO,EAAE;CAAE;CAClF,IAAI,QAAQ,YAAY,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,MAAM,MAAM,WAAW,OAAO,EAAE;CAAE;CACzF,IAAI,QAAQ,WAAW,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,WAAW,MAAM,cAAc,OAAO,EAAE;CAAE;CAChG,IAAI,QAAQ,UAAU,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,UAAU,MAAM,aAAa,OAAO,EAAE;CAAE;CAC7F,IAAI,QAAQ,SAAS;EACnB,MAAM,EAAE,KAAK,WAAW,aAAa,QAAQ,OAAO,QAAQ,KAAK;EAEjE,OAAO;GAAE,MAAM,CAAC;GAAG,UAAU,EAAE,OAAO;IAAE;IAAK;IAAQ,YAAY;IAAG,QAAQ;IAAO,OADrE,QAAQ,UAAU,QAAQ;GACiD,EAAE;EAAE;CAC/F;CACA,IAAI,QAAQ,SAAS;EACnB,MAAM,EAAE,KAAK,WAAW,aAAa,QAAQ,OAAO,QAAQ,KAAK;EAEjE,OAAO;GAAE,MAAM,CAAC;GAAG,UAAU,EAAE,UAAS,MADrB,QAAQ,QAAQ,WAAW,OAAO,MAAM,EAAA,CACd,IAAI,QAAQ,SAAS,EAAE;EAAE;CACxE;CACA,IAAI,QAAQ,SAAS,OAAO;EAAE,MAAM,CAAC;EAAG,UAAU,EAAE,SAAS,MAAM,QAAQ,OAAO,EAAE;CAAE;CACtF,OAAO;AACT;AAEA,SAAS,SAAS,EAAE,YAAY,WAAqC;CACnE,OAAO;EACL,MAAM,CAAC;EACP,MAAM,UAAU,UAAU;EAC1B,UAAU;GACR,OAAO;GACP,cAAc;IACZ,WAAW,CAAC,CAAC,QAAQ;IACrB,WAAW,CAAC,CAAC,QAAQ;IACrB,YAAY,CAAC,CAAC,QAAQ;IACtB,YAAY,OAAO,KAAK,QAAQ,cAAc,CAAC,CAAC;GAClD;EACF;CACF;AACF;AAEA,eAAe,QAAQ,EAAE,OAAO,OAAO,WAA4C;CACjF,MAAM,EAAE,KAAK,QAAQ,UAAU,aAAa,OAAO,KAAK;CAExD,MAAM,QAAuB,CAAC;CAC9B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,EAAE,QAAQ,eAAe,MAAM,YAAY,QAAQ,KAAK,KAAK,KAAK,MAAM,CAAC;EAC/E,MAAM,KAAK;GAAE,MAAM,KAAK;GAAM;GAAY,MAAM,OAAO,OAAO,EAAE,EAAE,KAAK,CAAC;EAAE,CAAC;CAC7E;CACA,MAAM,QAAQ,MAAM,YAAY,QAAQ,KAAK,MAAM,CAAC;CACpD,MAAM,KAAK;EAAE,MAAM;EAAY,YAAY,MAAM;EAAY,MAAM,MAAM,OAAO;CAAO,CAAC;CACxF,OAAO;AACT;AAEA,eAAe,WAAW,SAA2C;CACnE,MAAM,OAAO,MAAM,QACjB;;;;;;iDAOA,CAAC,CACH;CACA,MAAM,yBAAS,IAAI,IAAyB;CAC5C,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,OAAO,OAAO,IAAI,SAAS;EACjC,IAAI,QAAQ,OAAO,IAAI,IAAI;EAC3B,IAAI,CAAC,OAAO;GACV,QAAQ;IAAE;IAAM,MAAM,OAAO,IAAI,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI,SAAS;IAAS,SAAS,CAAC;GAAE;GAC7F,OAAO,IAAI,MAAM,KAAK;EACxB;EACA,MAAM,QAAQ,KAAK;GAAE,MAAM,OAAO,IAAI,UAAU;GAAG,MAAM,OAAO,IAAI,QAAQ;EAAE,CAAC;CACjF;CACA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC5B;;AAGA,MAAM,cAAc;AAEpB,MAAM,sBAAoC;CAAE,QAAQ,CAAC;CAAG,SAAS,CAAC;AAAE;;AAGpE,SAAS,eAAe,MAAc,KAA0C;CAC9E,OAAO,OAAO,QAAQ,OAAO,CAAC,CAAC,CAAC,CAC7B,QAAQ,GAAG,WAAW,OAAO,eAAe,KAAK,MAAM,QAAQ,CAAC,CAChE,KAAK,CAAC,YAAY,GAAG,KAAK,GAAG,QAAQ;AAC1C;;;;;AAMA,eAAe,YAAY,OAAiB,SAAmB;CAC7D,MAAM,UAAU,aAAa;CAC7B,MAAM,SAAiC,CAAC;CACxC,MAAM,UAAU,OAAO,KAAa,QAAgB;EAClD,IAAI;GACF,OAAO,MAAM,QAAQ,KAAK,CAAC,CAAC;EAC9B,SAAS,OAAO;GACd,OAAO,OAAO,UAAU,KAAK;GAC7B,OAAO;EACT;CACF;CACA,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,MAAM,GAAG;EACxD,MAAM,OAAO,MAAM,QAAQ,SAAS,QAAQ,kBAAkB,MAAM,IAAI,YAAY;EACpF,IAAI,CAAC,MAAM;EACX,QAAQ,OAAO,QAAQ,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC;EAChD,QAAQ,QAAQ,KAAK,GAAG,eAAe,MAAM,KAAK,EAAE,CAAC;CACvD;CACA,OAAO;EAAE;EAAS;CAAO;AAC3B;AAEA,eAAe,gBAAgB,EAAE,OAAO,WAAoB;CAC1D,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,OAAO,QAAQ,MAAM,IAAI,oBAAoB,oBAAoB,OAAO,KAAK,IAAI,GAAG;CACxF,OAAO,YAAY,OAAO,OAAO;AACnC;AAEA,eAAe,YAAY,EAAE,OAAO,WAA8C;CAChF,MAAM,aAA8B;EAAE,QAAQ,CAAC;EAAG,UAAU,CAAC;EAAG,SAAS,aAAa;CAAE;CACxF,MAAM,cAAc,cAAc,KAAK;CACvC,IAAI,YAAY,QAAQ;EACtB,WAAW,OAAO,QAAQ,YAAY,KAAK,IAAI;EAC/C,OAAO;CACT;CAEA,MAAM,UAAU,OAAO,KAAa,QAAgB;EAClD,IAAI;GACF,OAAO,MAAM,QAAQ,KAAK,CAAC,CAAC;EAC9B,SAAS,OAAO;GACd,WAAW,OAAO,OAAO,UAAU,KAAK;GACxC,OAAO;EACT;CACF;CAEA,MAAM,SAAS,MAAM,YAAY,OAAO,OAAO;CAC/C,WAAW,UAAU,OAAO;CAC5B,OAAO,OAAO,WAAW,QAAQ,OAAO,MAAM;CAC9C,MAAM,aAAa,OAAO,KAAK,MAAM,MAAM;CAE3C,KAAK,MAAM,CAAC,OAAO,kBAAkB,MAAM,iBAAiB,CAAC,EAAA,CAAG,QAAQ,GAAG;EACzE,MAAM,MAAM,gBAAgB;EAC5B,MAAM,OAAO,eAAe,aAAa,IAAI;EAC7C,MAAM,KAAK,eAAe,aAAa,EAAE;EACzC,IAAI,CAAC,QAAQ,CAAC,IAAI;EAClB,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,MACxB,QACC,WAAW,QAAQ,OAAO,IAAI,MAAM,EAAE,UACtC,CAAC,WAAW,QAAQ,OAAO,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAC7D;EACA,IAAI,SAAS;GACX,WAAW,OAAO,OAAO,aAAa,QAAQ,MAAM,sBAAsB,QAAQ,OAAO;GACzF;EACF;EAEA,MAAM,QAAQ,MAAM,OAAO,GAAG,MAAM,EAAE;EACtC,IAAI,CAAC,SAAS,WAAW,OAAO,SAAS,GAAG,UAAU;EACtD,MAAM,WAAW,MAAM,QACrB,KACA,YAAY,GAAG,OAAO,gBAAgB,MAAM,cAAc,GAAG,OAAO,sDACtE;EACA,IAAI,UAAU,QACZ,WAAW,OAAO,OAChB,aAAa,aAAa,GAAG,wBAAwB,OAAO,eAAe,SAAS,EAAE,CAAC,CAAC,CAAC;CAC/F;CAEA,KAAK,MAAM,CAAC,MAAM,cAAc,OAAO,QAAQ,MAAM,UAAU,GAAG;EAChE,MAAM,QAAQ,iBAAiB,UAAU,KAAK,UAAU,CAAC,CAAC;EAC1D,IAAI,WAAW,OAAO,SAAS,UAAU;EACzC,MAAM,MAAM,UAAU,UAAU,IAAI,6BAA6B,MAAM,OAAO,MAAM,EAAE,IAAI,YAAY,YAAY,MAAM,MAAM,WAAW,UAAU,IAAI;EAEvJ,MAAM,SAAQ,MADK,QAAQ,aAAa,QAAQ,GAAG,EAAA,GAC9B,EAAE,EAAE;EACzB,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM;EAC3C,MAAM,WAAW,OAAO,UAAU,YAAY,OAAO,UAAU;EAC/D,IAAI,UAAU,SAAS,YAAY,CAAC,YAAY,OAAO,MAAM,OAAO,KAAK,CAAC,GACxE,WAAW,SAAS,aAAa,UAAU,sCAAsC,MAAM;EACzF,IAAI,UAAU,SAAS,YAAY,UACjC,WAAW,SAAS,aAAa,UAC/B;CACN;CAEA,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,MAAM,QAAQ,GAAG;EAC5D,IAAI,CAAC,YAAY,OAAO,GAAG;EAI3B,IAHe,iBAAiB,QAAQ,KAAK,UAAU,CAAC,CAAC,MACtD,UAAU,WAAW,OAAO,SAAS,QAE/B,GAAG;EACZ,MAAM,EAAE,KAAK,WAAW,aACtB,OACA;GAAE,OAAO,MAAM;GAAM,UAAU,CAAC,IAAI;EAAE,GACtC,EAAE,aAAa,YAAY,CAC7B;EACA,IAAI;GACF,MAAM,QAAQ,KAAK,MAAM;EAC3B,SAAS,OAAO;GACd,WAAW,OAAO,WAAW,UAAU,UAAU,KAAK;EACxD;CACF;CAEA,OAAO;AACT;AAEA,eAAe,UAAU,EAAE,YAAY,OAAO,SAAS,WAAoB;CACzE,IAAI,CAAC,QAAQ,WAAW,MAAM,IAAI,oBAAoB,uCAAuC;CAC7F,IAAI,CAAC,QAAQ,OAAO,MAAM,IAAI,oBAAoB,mCAAmC;CACrF,IAAI,MAAM,SAAS,WAAW,MAC5B,MAAM,IAAI,oBAAoB,6CAA6C;CAE7E,MAAM,OAAO,MAAM,QAAQ,WAAW,GAAG,WAAW,KAAK,IAAI;CAC7D,MAAM,EAAE,UAAU,cAAc,MAAM,GAAG;CACzC,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,SAAS,MAAM,MAAM;CACxC,QAAQ;EACN,MAAM,IAAI,oBAAoB,qCAAqC,MAAM;CAC3E;CAEA,IAAI;EACF,MAAM,WAAW,mBAAmB,UAAU,KAAK;EACnD,MAAM,EAAE,aAAa;EACrB,MAAM,UAAU,MAAM,YAAY,SAAS,SAAS,MAAM,UAAU,OAAO;EAC3E,MAAM,UAAU,QAAQ,SAAS,WAAW,YAAY;EACxD,IAAI,SAAS,MAAM,UAAU,MAAM,SAAS,MAAM;EAClD,OAAO;GAAE;GAAM;GAAU;GAAS;GAAS;EAAS;CACtD,SAAS,OAAO;EACd,IAAI,iBAAiB,gBAAgB,MAAM,IAAI,oBAAoB,MAAM,OAAO;EAChF,MAAM;CACR;AACF;AAEA,eAAe,WAAW,EAAE,OAAO,SAAS,WAAoB;CAC9D,IAAI,CAAC,QAAQ,YAAY,MAAM,IAAI,oBAAoB,wCAAwC;CAC/F,MAAM,EAAE,QAAQ,SAAS,QAAQ,cAAe,CAAC;CACjD,MAAM,QAAQ,kBAAkB,QAAQ,UAAU;CAClD,MAAM,SAAS,SAAS,eAAe,QAAQ,KAAK,IAAI,CAAC,iCAAiC;CAC1F,IAAI,OAAO,QAAQ,MAAM,IAAI,oBAAoB,oBAAoB,OAAO,KAAK,IAAI,GAAG;CACxF,MAAM,QAAQ;CAEd,MAAM,OAAO,MAAM,QAAQ,YAAY,GAAG,MAAM,GAAG,MAAM;CACzD,MAAM,EAAE,UAAU,cAAc,MAAM,GAAG;CACzC,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,SAAS,MAAM,MAAM;CACxC,QAAQ;EACN,MAAM,IAAI,oBAAoB,qCAAqC,MAAM;CAC3E;CAEA,MAAM,UAAU,MAAM,YAAY,kBAAkB,KAAK,GAAG,MAAM,UAAU,OAAO;CACnF,MAAM,UAAU,SAAS,WAAW,YAAY;CAChD,IAAI,SAAS,MAAM,UAAU,MAAM,SAAS,MAAM;CAElD,OAAO;EAAE;EAAM;EAAU;EAAS;EAAS,UAAU,oBAAoB,OAAO,OAAO,KAAK;CAAE;AAChG;AAEA,MAAM,eAAe,SAAgC,UAAkB;CACrE,IAAI,CAAC,QAAQ,cACX,MAAM,IAAI,oBAAoB,0CAA0C;CAC1E,OAAO,MAAM,QAAQ,cAAc,KAAK;AAC1C;AAEA,eAAe,aAAa,EAAE,YAAY,OAAO,SAAS,SAAS,WAAoB;CACrF,MAAM,EAAE,MAAM,cAAc,QAAQ,YAAY,EAAE,MAAM,GAAG;CAC3D,IAAI,CAAC,KAAK,KAAK,IAAI,GACjB,MAAM,IAAI,oBAAoB,2DAAuD;CACvF,MAAM,MAAM,YAAY,SAAS,WAAW,IAAI;CAChD,MAAM,OAAO,GAAG,IAAI,GAAG,KAAK;CAC5B,MAAM,EAAE,OAAO,UAAU,cAAc,MAAM,GAAG;CAEhD,IAAI,CAAC,WAKC;MAAA,MAJiB,SAAS,MAAM,MAAM,CAAC,CAAC,WACpC,YACA,KACR,GACY,MAAM,IAAI,oBAAoB,0BAA0B,KAAK,EAAE;CAAA;CAI7E,MAAM,WAA8B;EAClC;EACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EAClC,OAAO;GACL,OAAO,WAAW;GAClB,YAAY,MAAM,cAAc,CAAC;GACjC,UAAU,MAAM,YAAY,CAAC;GAC7B,SAAS,MAAM,WAAW,CAAC;EAC7B;EACA,MAAM,MAAM,QAAQ,UAAU;CAChC;CACA,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CACpC,MAAM,UAAU,MAAM,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,KAAK,MAAM;CACtE,OAAO,EAAE,KAAK;AAChB;AAEA,eAAe,cAAc,EAAE,YAAY,WAAkD;CAC3F,MAAM,MAAM,YAAY,SAAS,WAAW,IAAI;CAChD,MAAM,EAAE,SAAS,aAAa,MAAM,GAAG;CACvC,MAAM,QAAQ,MAAM,QAAQ,GAAG,CAAC,CAAC,YAAY,CAAC,CAAa;CAS3D,OAAO,MARiB,QAAQ,IAC9B,MACG,QAAQ,SAAS,KAAK,SAAS,OAAO,CAAC,CAAC,CACxC,KAAK,CAAC,CACN,IACC,OAAO,SAAS,KAAK,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,QAAQ,MAAM,CAAC,CACrE,CACJ;AAEF;;;AC/YA,MAAM,oBAAoB;AAC1B,MAAM,wBAAQ,IAAI,IAAkE;AAEpF,SAAS,UAAU,KAAa,KAAa,MAAgC;CAC3E,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,MAAM,MAAM,IAAI,GAAG;CACzB,IAAI,OAAO,IAAI,YAAY,KAAK,OAAO;EAAE,WAAW,IAAI;EAAW,QAAQ;CAAK;CAEhF,IAAI,MAAM,QAAQ,mBAAmB;EACnC,KAAK,MAAM,CAAC,GAAG,UAAU,OAAO,IAAI,MAAM,aAAa,KAAK,MAAM,OAAO,CAAC;EAC1E,IAAI,MAAM,QAAQ,mBAAmB,MAAM,OAAO,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAe;CACvF;CAEA,MAAM,YAAY,KAAK;CACvB,MAAM,IAAI,KAAK;EAAE,WAAW,MAAM;EAAK;CAAU,CAAC;CAClD,UAAU,YAAY,MAAM,OAAO,GAAG,CAAC;CACvC,OAAO;EAAE;EAAW,QAAQ;CAAM;AACpC;AAEA,eAAsB,SACpB,QACA,OACA,SACA,UAA2B,CAAC,GACF;CAG1B,MAAM,EAAE,OAAO,GAAG,eAAe,OAAO,YAAY,CAAC;CACrD,MAAM,WAAW,QAAQ,WAAW,OAAO,WAAW,KAAA;CACtD,IAAI,CAAC,QAAQ,YAAY,OAAO,OAAO,UAAU,CAAC,CAAC,KAAK,OAAO,GAC7D,MAAM,IAAI,oBAAoB,sCAAsC;CAEtE,MAAM,aAAa,OAAO,OAAO;CACjC,IAAI,CAAC,YAAY,MAAM,IAAI,oBAAoB,yBAAyB,OAAO,MAAM,EAAE;CAEvF,MAAM,QAAQ,UAAU;CACxB,IAAI,OAAO;EACT,MAAM,SAAS,cAAc,KAAK;EAClC,IAAI,OAAO,QAAQ,MAAM,IAAI,oBAAoB,sBAAsB,OAAO,KAAK,IAAI,GAAG;CAC5F;CACA,MAAM,QAAQ,SAAS;CAGvB,IAAI,MAAM,MAAM,OAAO;EAAE,MAAM,CAAC;EAAG,MAAM,UAAU,KAAK;CAAE;CAE1D,MAAM,MAAM,QAAQ,cAAc;CAClC,MAAM,MAAM,OAAO,WAAqB;EACtC,MAAM,EAAE,KAAK,WAAW,aAAa,QAAQ,KAAK;EAClD,MAAM,OAAO,YAAgC;GAC3C,MAAM,YAAY,YAAY,IAAI;GAClC,MAAM,OAAQ,MAAM,QAAQ,KAAK,MAAM;GACvC,OAAO;IAAE,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS;IAAG,MAAM,KAAK,IAAI,YAAY;GAAE;EAC/F;EAEA,MAAM,EAAE,WAAW,WACjB,MAAM,IACF,UAAU,KAAK,UAAU,CAAC,KAAK,MAAM,CAAC,GAAG,KAAK,IAAI,IAClD;GAAE,WAAW,KAAK;GAAG,QAAQ;EAAM;EACzC,OAAO;GAAE;GAAK;GAAQ;GAAQ,GAAI,MAAM;EAAW;CACrD;CAEA,IAAI,UAAU;EACZ,IAAI,SAAS,YAAY,MAAM,MAAM;EACrC,IAAI;GACF,MAAM,UAAU,MAAM,sBAAsB;IAC1C;IACA;IACA;IACA,SAAS;IACT,UAAU,KAAK,WAAW,QAAQ,KAAK,MAAM;IAC7C,WAAW;IACX,SAAS,OAAO,QAAQ,aAAa,WAAW,QAAQ,WAAW,CAAC;IACpE,SAAS,OAAO,YAAY,MAAM,IAAI,MAAM,EAAA,CAAG;GACjD,CAAC;GACD,IAAI,SAAS,OAAO;EACtB,SAAS,OAAO;GAEd,IAAI,iBAAiB,qBAAqB,MAAM;GAChD,MAAM,IAAI,oBAAoB,UAAU,KAAK,CAAC;EAChD;CACF;CAEA,MAAM,EAAE,MAAM,KAAK,QAAQ,YAAY,WAAW,MAAM,IAAI,KAAK,CAAC,CAAC,OAAO,UAAU;EAElF,IAAI,CAAC,YAAY,iBAAiB,qBAAqB,MAAM;EAC7D,MAAM,IAAI,oBAAoB,UAAU,KAAK,CAAC;CAChD,CAAC;CACD,IAAI,CAAC,UAAU,OAAO,OAAO,EAAE,KAAK;CACpC,OAAO;EAAE;EAAM,UAAU,EAAE,OAAO;GAAE;GAAK;GAAQ;GAAY;GAAQ,OAAO,CAAC,CAAC;EAAM,EAAE;CAAE;AAC1F"}