@bluemarble/bi 0.2.0 → 0.2.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.
- package/dist/server/index.js +20 -6
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -338,6 +338,21 @@ const timed = async (load) => {
|
|
|
338
338
|
};
|
|
339
339
|
};
|
|
340
340
|
const SLUG = /^[A-Za-z0-9_-]{1,80}$/;
|
|
341
|
+
/** `<pasta>/<arquivo>`, aceitando pasta com `/` ou `\` no fim (`path.join` no Windows). */
|
|
342
|
+
const inDir = (dir, file) => `${dir.replace(/[\\/]+$/, "")}/${file}`;
|
|
343
|
+
/**
|
|
344
|
+
* Conteúdo final do arquivo: formatado pelo app (se der erro, sai sem formatar) e com o fim de
|
|
345
|
+
* linha do arquivo atual — no Windows o checkout costuma vir com CRLF, e o gerador/formatador
|
|
346
|
+
* devolvem LF, o que faria o diff marcar o arquivo inteiro.
|
|
347
|
+
*/
|
|
348
|
+
async function fileContent(raw, path, previous, options) {
|
|
349
|
+
const { format } = options;
|
|
350
|
+
const content = (format ? await Promise.resolve().then(() => format(raw, path)).catch((error) => {
|
|
351
|
+
console.warn("[@bluemarble/bi] format falhou; o arquivo sai sem formatar:", error);
|
|
352
|
+
return raw;
|
|
353
|
+
}) : raw).replace(/\r\n/g, "\n");
|
|
354
|
+
return previous.includes("\r\n") ? content.replace(/\n/g, "\r\n") : content;
|
|
355
|
+
}
|
|
341
356
|
async function handleDevtoolsRequest(context) {
|
|
342
357
|
const { request } = context;
|
|
343
358
|
if (request.describe) return describe(context);
|
|
@@ -557,7 +572,7 @@ async function saveModel({ registered, model, request, options }) {
|
|
|
557
572
|
if (!options.modelsDir) throw new DashboardQueryError("O endpoint não configurou `modelsDir`");
|
|
558
573
|
if (!request.model) throw new DashboardQueryError("Nada para salvar: não há rascunho");
|
|
559
574
|
if (model.name !== registered.name) throw new DashboardQueryError("O rascunho não pode trocar o nome do modelo");
|
|
560
|
-
const path =
|
|
575
|
+
const path = inDir(options.modelsDir, `${registered.name}.ts`);
|
|
561
576
|
const { readFile, writeFile } = await fs();
|
|
562
577
|
let previous;
|
|
563
578
|
try {
|
|
@@ -568,7 +583,7 @@ async function saveModel({ registered, model, request, options }) {
|
|
|
568
583
|
try {
|
|
569
584
|
const replaced = replaceModelInFile(previous, model);
|
|
570
585
|
const { warnings } = replaced;
|
|
571
|
-
const content =
|
|
586
|
+
const content = await fileContent(replaced.content, path, previous, options);
|
|
572
587
|
const written = request.save === "write" && content !== previous;
|
|
573
588
|
if (written) await writeFile(path, content, "utf8");
|
|
574
589
|
return {
|
|
@@ -590,7 +605,7 @@ async function saveLayout({ model, request, options }) {
|
|
|
590
605
|
const errors = layout ? validateLayout(layout, types) : ["Nada para salvar: não há layout"];
|
|
591
606
|
if (errors.length) throw new DashboardQueryError(`Layout inválido: ${errors.join("; ")}`);
|
|
592
607
|
const valid = layout;
|
|
593
|
-
const path =
|
|
608
|
+
const path = inDir(options.layoutsDir, `${valid.id}.json`);
|
|
594
609
|
const { readFile, writeFile } = await fs();
|
|
595
610
|
let previous;
|
|
596
611
|
try {
|
|
@@ -598,8 +613,7 @@ async function saveLayout({ model, request, options }) {
|
|
|
598
613
|
} catch {
|
|
599
614
|
throw new DashboardQueryError(`Arquivo do layout não encontrado: ${path}`);
|
|
600
615
|
}
|
|
601
|
-
const
|
|
602
|
-
const content = options.format ? await options.format(raw, path) : raw;
|
|
616
|
+
const content = await fileContent(layoutFileContent(valid), path, previous, options);
|
|
603
617
|
const written = mode === "write" && content !== previous;
|
|
604
618
|
if (written) await writeFile(path, content, "utf8");
|
|
605
619
|
return {
|
|
@@ -612,7 +626,7 @@ async function saveLayout({ model, request, options }) {
|
|
|
612
626
|
}
|
|
613
627
|
const snapshotDir = (options, model) => {
|
|
614
628
|
if (!options.snapshotsDir) throw new DashboardQueryError("O endpoint não configurou `snapshotsDir`");
|
|
615
|
-
return
|
|
629
|
+
return inDir(options.snapshotsDir, model);
|
|
616
630
|
};
|
|
617
631
|
async function saveSnapshot({ registered, query, request, options, runRows }) {
|
|
618
632
|
const { name, overwrite } = request.snapshot ?? { name: "" };
|
package/dist/server/index.js.map
CHANGED
|
@@ -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\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 = `${options.modelsDir.replace(/\\/+$/, '')}/${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 = options.format ? await options.format(replaced.content, path) : replaced.content\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 = `${options.layoutsDir.replace(/\\/+$/, '')}/${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 raw = layoutFileContent(valid)\n const content = options.format ? await options.format(raw, path) : raw\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 `${options.snapshotsDir.replace(/\\/+$/, '')}/${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.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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"}
|