npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agility-luhn/compiler

v2.2.0

Published

Compiler and generators for the LUHN domain DSL.

Readme

@luhn/compiler

Compilador da DSL LUHN: lê arquivos .luhn, valida semanticamente, expõe AST resolvida e emite artefatos para TypeScript (interfaces + Zod + service + controller), SQL (DDL Postgres), OpenAPI 3.1 e um índice canônico de metadados.

Pacote independente e publicavel no npm. Os artefatos de distribuicao ficam em dist/ (main: "dist/index.js", types: "dist/index.d.ts").


1. Sumário das Capacidades

| Capacidade | Implementação | |---------------------------------------------|---------------| | Parser de .luhn | src/lexer.ts, src/parser.ts | | Análise semântica e resolução de símbolos | src/semantic.ts | | Loader de arquivos por application+module | src/file-loader.ts | | API principal (compile, tipos) | src/compiler.ts, src/index.ts | | Mini-linguagem implementation script | src/script.ts | | Watcher com debounce | src/watcher.ts | | Geração TypeScript (model/service/controller/Zod) | src/generator/typescript-generator.ts | | Geração SQL (Postgres) | src/generator/sql-generator.ts | | Geração de schema canônico do banco | src/generator/database-schema-generator.ts |a | Geração OpenAPI 3.1 | src/generator/openapi-generator.ts | | Geração de metadata leve | src/generator/metadata-generator.ts | | Mapa canônico (introspecção em runtime) | src/generator/canonical-map-generator.ts | | Validadores/fills e máscaras Delphi em type/campo | src/parser.ts, src/semantic.ts, src/generator/canonical-map-generator.ts | | Configuration declarativo (configuration) com AST dedicada e geração orientada a business | src/ast.ts, src/parser.ts, src/semantic.ts | | Validação Zod isolada de controller | src/controller-zod.ts | | Protocolo de plugins (extensões em build) | src/plugin.ts |


2. API Pública

import { compile, CompileOptions, CompileResult } from '@luhn/compiler';

const result = await compile('./luhn', {
  setup,            // LuhnGenerationSetup (multitenancy, runtime importPath, driver SQL)
  typescript: true, // gera *.interface.ts, *.schema.ts, *.model.ts, *.handle.ts
  sql: true,        // gera generated/database/schema.sql por modulo
  openapi: true,    // gera generated/openapi/openapi.json por modulo
  metadata: true,   // gera generated/metadata/metadata.json por modulo
  outputDir: './src/luhn'
});

if (result.diagnostics.some(d => d.severity === 'error')) {
  for (const d of result.diagnostics) console.error(d.code, d.message);
}

CompileResult expõe:

  • program — AST bruta.
  • resolved — AST após resolveProgram (símbolos resolvidos, tipos normalizados, mixins aplicados).
  • diagnostics{ code, severity, message, span: { filePath, startLine, startColumn } }[].
  • artifacts — mapa path -> conteúdo de todos os arquivos emitidos antes da gravação.

Diretivas auxiliares (re-exportadas por src/index.ts): parseProgram, resolveProgram, loadApplicationInputs, createWatcher, compileScript, geradores individuais e tipos (Program, ResolvedProgram, LuhnGenerationSetup, Diagnostic, Span).


3. Linguagem LUHN (estado atual)

3.1 Declarações top-level

application, module, import, enum, type, record, business, configuration, mixin, service, library.

Regras estruturais aplicadas pelo semantic:

  • exatamente uma application.
  • toda declaração precisa pertencer a um module.
  • application.modules: [...] deve bater com os module <nome> declarados (mão dupla).
  • ciclo entre módulos é erro.
  • import precisa resolver para símbolo ou módulo existente.
  • referências por FQCN em campo/parametro/mixin target são proibidas — exige import + nome curto.
  • mixin com colisão de campos → erro.
  • default precisa ser literal compatível com o tipo (enum aceita só membro válido).
  • service implementation script exige corpo.

3.1.1 Configuration (configuration)

configuration e uma declaracao de alto nivel para configuracoes operacionais vinculadas a um business, mas no backend de geracao reaproveita a mesma semantica de persistencia/CRUD de business.

Diretrizes de uso:

  • Nome no formato business.nomeConfig (sem FQCN completo no corpo, resolucao por import).
  • @scope segue as regras existentes (global, conta, unidade) e aproveita o setup de multitenancy.
  • O AST preserva kind = config para diferenciar no semantic/geradores.
  • A estrategia de persistencia pode ser especializada por gerador (ex.: tabela unificada de configuracoes por modulo) sem perder o contrato de CRUD/OData do runtime.

Exemplo:

import exchange.message

@summary "Roteamento por tipo de mensagem"
@scope conta
configuration message.routeByType
  messageType: notificationCode
  channel: channel
  provider: provider
  active: boolean = true
  priority: smallint = 0

3.2 Tipos primitivos reconhecidos

uuid, string, text, number, smallint, float, money, boolean, date, timestamp, json, xml, url, image, crypto, vector, file, storage, any.

O resolver normaliza primitivos em TS (string/number/boolean/unknown) e gera os tipos físicos apropriados no SQL e no OpenAPI.

3.3 Campos e parâmetros

nome?: tipo[] = valor "Descricao" preferred
  • ? opcional.
  • [] array (gera child_table no SQL salvo embedded_record).
  • default literal (string, number, boolean, ref a enum).
  • string final = descrição de exibição.
  • preferred marca o campo "principal" para apresentação.
  • validators.<funcao> associa validador declarativo ao type alias ou ao campo.
  • fills.<funcao>(campo1,campo2,...) associa preenchimento automático declarativo.
  • = (expressao) marca o campo como calculado.
  • campos calculados aceitam aritmética + - * /, caminho de campo (a.b) e funções sum, avg, min, max, coalesce.
  • o valor calculado é numérico no runtime (entrada textual numérica é convertida; vazio/null vira 0).
  • string com tokens Delphi (0, 9, L, l, A, a, C, c, #) define máscara de input.
  • expressão booleana entre parênteses no fim do campo controla visibilidade condicional.

Exemplos:

type cpf string 11 validators.cpf

record pessoa
  documento: string 20 validators.cnpj

record endereco
  cep: string 9 fills.cep(logradouro,bairro,cidade,estado) "00000-000"

record item
  quantidade: number
  preco: money
  total: money = (preco * quantidade)

business comercial.saida "Saidas"
  produtos: item[]
  total: money = (sum(produtos.total))

record cliente
  tipoPessoa: tipoPessoa
  cpf?: cpf (tipoPessoa == FISICA)
  cnpj?: cnpj (tipoPessoa == JURIDICA)

Precedência de validação:

  • se o campo declara validators.<funcao>, ele prevalece.
  • se o campo não declara, herda validação do type alias (quando houver).

Precedência de fill:

  • se o campo declara fills.<funcao>(...), ele prevalece.
  • se o campo não declara, herda fill do type alias (quando houver).

Campos calculados: funções e BNF

Funções suportadas:

  • sum(caminho) soma os valores resolvidos no caminho (inclui coleção).
  • avg(caminho) média aritmética dos valores resolvidos.
  • min(caminho) menor valor numérico do caminho.
  • max(caminho) maior valor numérico do caminho.
  • coalesce(a, b, c, ...) retorna o primeiro argumento não nulo/vazio.

BNF específica de expressão calculada:

<calculated-clause> ::= "=" "(" <calc-expr> ")"

<calc-expr> ::= <calc-term> (("+" | "-") <calc-term>)*
<calc-term> ::= <calc-unary> (("*" | "/") <calc-unary>)*
<calc-unary> ::= ("+" | "-") <calc-unary> | <calc-primary>

<calc-primary> ::= NUMBER
                 | <path>
                 | "(" <calc-expr> ")"
                 | <calc-func>

<calc-func> ::= ("sum" | "avg" | "min" | "max") "(" <path> ")"
              | "coalesce" "(" <calc-args>? ")"

<calc-args> ::= <calc-expr> ("," <calc-expr>)*
<path> ::= IDENT ("." IDENT)*

3.4 Business e records

@scope conta
business financeiro.contaReceber "Contas a Receber"
  numero: string
  cliente: cliente
  • business recebe campo sintético id (UUID, role primary_key).
  • Records 1-nível sem arrays são candidatos a embedded_record (achatado em colunas prefixadas no pai).
  • Records com arrays caem para TEXT JSON.
  • Arrays de records → tabela filha com parent_id + item_index.

3.5 Services

service modulo.nome "Label"
  description "..."
  input
    campo: tipo
  output
    campo: tipo
  throws
    CODIGO "Mensagem"
  implementation code      # handler TS escrito a mão
  # ou
  implementation script    # mini-linguagem transpilada
    ...

3.6 Libraries

library modulo.nome "Label"
  function nome
    input
    output

3.7 Validadores e fills declarativos

O parser aceita validators.<funcao> como sufixo de type alias escalar e de campo. Também aceita fills.<funcao>(campo1,campo2,...) como sufixo de type alias escalar e de campo.

O semantic resolve a referência para uma library e função reais e valida:

  • library inexistente (SEM019)
  • função inexistente na library (SEM020)
  • ambiguidade de resolução da library (SEM021)

O semantic resolve referências de fill e valida:

  • library inexistente (SEM022)
  • função inexistente na library (SEM023)
  • ambiguidade de resolução da library (SEM024)

Após resolução, o compilador propaga para o modelo resolvido:

validate?: { library: string; fn: string }

fill?: { library: string; fn: string; targets: readonly string[] }

mask?: string

visibleWhen?: string

Essa metadata segue para o objects-map.json e é consumida no runtime/UI para:

  • validação assíncrona no client (blur);
  • preenchimento automático no client (blur);
  • validação server-side no CRUD antes da persistência.

Diagrama do fluxo:

flowchart LR
  A[LUHN: type/campo com validators.funcao e fills.funcao] --> B[Parser]
  B --> C[Semantic resolve library + funcao]
  C --> D{Valido?}
  D -- nao --> E[Diagnostics SEM019-SEM024]
  D -- sim --> F[ResolvedField/ResolvedType com validate fill mask]
  F --> G[CanonicalMapGenerator]
  G --> H[objects-map.json com field.validate field.fill field.mask field.visibleWhen]
  H --> I[Runtime Describe]
  I --> J[UI: async validator e autofill no blur]
  H --> K[Runtime CRUD create/update/patch]

3.8 Máscaras no formato Delphi

Quando o parser encontra um sufixo STRING contendo tokens de máscara Delphi, o valor é tratado como máscara do campo/tipo (sem @mask e sem keyword extra).

Exemplo:

type cpf string 14 validators.cpf "000.000.000-00"

record endereco
  cep: string 9 fills.cep(logradouro,bairro,cidade,estado) "00000-000"

Tokens suportados:

  • 9: número opcional
  • 0: número obrigatório
  • L: letra obrigatória
  • l: letra opcional
  • A: letra ou número
  • a: letra ou número opcional
  • C: qualquer caractere
  • c: qualquer caractere opcional
  • #: número com sinal (+/-)
  • literais fixos: :, /, -, . (e demais símbolos do template)

Diagrama do fluxo de máscara:

flowchart LR
  A[Campo com string de mascara Delphi] --> B[Parser detecta mask template]
  B --> C[Semantic aplica heranca de alias quando necessario]
  C --> D[ResolvedField ou ResolvedType com mask]
  D --> E[CanonicalMapGenerator]
  E --> F[field.mask no objects-map]
  F --> G[Runtime describe]
  G --> H[UI aplica formatacao no input]

3.9 Mini-linguagem implementation script

Statements: let, return, throw CODIGO, log, call modulo.library.funcao [with expr], if (inline), for ... from ... to ... (inline ou bloco), push array expr, expression statement.

Expressões: literais, acesso qualificado a.b.c, métodos obj.metodo(arg), arrays e object literals (suportam multiline via balanceamento), operadores aritméticos + - * /, comparação == != < <= > >=, lógicos and or not, call como expressão (transpilada com await). Comentários // e #.

Validações: throw CODIGO só aceita códigos declarados no bloco throws. Transpila para TypeScript chamando defineService(...) do runtime.

3.10 Gramática (BNF)

BNF simplificada da linguagem LUHN (baseada em src/lexer.ts + src/parser.ts).

Observações léxicas relevantes:

  • blocos usam INDENT/DEDENT (não usa {});
  • comentários de linha: // ... e # ...;
  • strings usam aspas duplas;
  • ScriptBody é token especial capturado após implementation script.
<program> ::= <statement>* EOF

<statement> ::= <annotations>? (
    <application>
  | <import>
  | <enum>
  | <type-alias>
  | <record>
  | <business>
  | <mixin>
  | <service>
  | <library>
)

<annotations> ::= <annotation>+
<annotation> ::= "@" <qualified-name> <annotation-value>?
<annotation-value> ::= STRING | NUMBER | BOOLEAN | <qualified-name>

<application> ::= "application" IDENT <application-block>?
<application-block> ::= INDENT <application-item>* DEDENT
<application-item> ::= 
    "title" STRING
  | "description" STRING
  | "version" STRING
  | "database" IDENT
  | "types" STRING
  | "modules" <qualified-name-list>
  | <application-module-decl>

<application-module-decl> ::= "module" <qualified-name> <module-block>?
<module-block> ::= INDENT <module-item>* DEDENT
<module-item> ::= 
    "title" STRING
  | "owner" BOOLEAN
  | "enabled" BOOLEAN
  | "types" STRING
  | "depends" "on" <qualified-name-list>

<import> ::= "import" <qualified-name> ("as" IDENT)?

<enum> ::= "enum" IDENT INDENT <enum-entry>+ DEDENT
<enum-entry> ::= NUMBER ":" IDENT

<type-alias> ::= "type" IDENT (
    INDENT <field>* DEDENT
  | <type-ref> NUMBER? <type-alias-suffix>*
)
<type-alias-suffix> ::= "preferred" | <validator-ref> | <fill-ref> | <mask-template>

<record> ::= "record" IDENT STRING? <fields-block>?
<business> ::= "business" IDENT STRING? <fields-block>?
<mixin> ::= "mixin" <qualified-name> <fields-block>?
<fields-block> ::= INDENT <field>* DEDENT

<service> ::= "service" <service-name> STRING? <service-block>?
<service-name> ::= IDENT | IDENT "." IDENT
<service-block> ::= INDENT <service-item>* DEDENT
<service-item> ::= 
    "describe"
  | "description" STRING
  | "input" <parameters-block>
  | "output" <parameters-block>
  | "throws" <throws-block>
  | "implementation" ("code" | "script" ScriptBody?)

<library> ::= "library" IDENT STRING? <library-block>?
<library-block> ::= INDENT <library-item>* DEDENT
<library-item> ::= "description" STRING | <library-function>

<library-function> ::= <annotations>? "function" IDENT <library-function-block>?
<library-function-block> ::= INDENT <library-function-item>* DEDENT
<library-function-item> ::= 
    "description" STRING
  | "input" <parameters-block>
  | "output" <parameters-block>

<parameters-block> ::= INDENT <parameter>* DEDENT
<parameter> ::= <annotations>? IDENT "?"? ":" <type-ref> <string-size>? <array-suffix>? <default-clause>?

<throws-block> ::= INDENT <throw-item>* DEDENT
<throw-item> ::= IDENT STRING?

<field> ::= <annotations>? IDENT "?"? ":" <type-ref> <array-suffix>? <string-size>? (<default-clause> | <calculated-clause>)? <field-suffix>*
<field-suffix> ::= STRING | "preferred" | <validator-ref> | <fill-ref> | <mask-template> | <visibility-expr>

<validator-ref> ::= "validators" "." IDENT
<fill-ref> ::= "fills" "." IDENT "(" <identifier-list> ")"
<mask-template> ::= STRING  ; quando STRING contiver tokens Delphi de máscara
<visibility-expr> ::= "(" <boolean-expr> ")"

<boolean-expr> ::= <boolean-term> ("or" <boolean-term>)*
<boolean-term> ::= <boolean-factor> ("and" <boolean-factor>)*
<boolean-factor> ::= "not" <boolean-factor> | "(" <boolean-expr> ")" | <comparison>
<comparison> ::= IDENT "==" IDENT | IDENT "==" "true" | IDENT "==" "false" | IDENT

<type-ref> ::= IDENT ("." IDENT)*
<qualified-name> ::= IDENT ("." IDENT)*
<qualified-name-list> ::= <qualified-name> ("," <qualified-name>)*
<identifier-list> ::= IDENT ("," IDENT)*

<array-suffix> ::= "[" "]"
<string-size> ::= NUMBER
<default-clause> ::= "=" <literal>
<calculated-clause> ::= "=" "(" <calc-expr> ")"
<literal> ::= STRING | NUMBER | BOOLEAN | <qualified-name>

<calc-expr> ::= <calc-term> (("+" | "-") <calc-term>)*
<calc-term> ::= <calc-unary> (("*" | "/") <calc-unary>)*
<calc-unary> ::= ("+" | "-") <calc-unary> | <calc-primary>
<calc-primary> ::= NUMBER | <qualified-name> | "(" <calc-expr> ")" | <calc-func>
<calc-func> ::= ("sum" | "avg" | "min" | "max") "(" <qualified-name> ")"
              | "coalesce" "(" (<calc-expr> ("," <calc-expr>)*)? ")"

BOOLEAN ::= "true" | "false"
IDENT ::= /[A-Za-z_][A-Za-z0-9_]*/
NUMBER ::= /-?[0-9]+(\.[0-9]+)?/

Recorte BNF específico de fills e máscara

Para deixar o recurso de autofill explícito na gramática, este é o recorte mínimo:

<type-alias-suffix> ::= "preferred" | <validator-ref> | <fill-ref> | <mask-template>
<field-suffix> ::= STRING | "preferred" | <validator-ref> | <fill-ref> | <mask-template> | <visibility-expr>

<fill-ref> ::= "fills" "." IDENT "(" <fill-target-list> ")"
<fill-target-list> ::= IDENT ("," IDENT)*
<mask-template> ::= STRING ; tokens Delphi: 9 0 L l A a C c #
<visibility-expr> ::= "(" <boolean-expr> ")"

Diagrama dedicado do fluxo de fills no compilador:

flowchart LR
  A[DSL: fills.funcao targets] --> B[Parser reconhece fill-ref]
  B --> C[Semantic resolve library e funcao]
  C --> D{Referencia valida?}
  D -- nao --> E[SEM022 SEM023 SEM024]
  D -- sim --> F[ResolvedField ou ResolvedType com fill]
  F --> G[CanonicalMapGenerator]
  G --> H[field.fill com library fn targets]

Diagrama estrutural da gramática:

flowchart TD
  P[program] --> S[statement*]

  S --> A[application]
  S --> I[import]
  S --> E[enum]
  S --> T[type-alias]
  S --> R[record]
  S --> B[business]
  S --> M[mixin]
  S --> SV[service]
  S --> L[library]

  T --> TR[type-ref]
  T --> F[field*]
  T --> TS[type-alias-suffix*]
  TS --> PREF[preferred]
  TS --> VR[validator-ref]

  R --> FB[fields-block]
  B --> FB
  M --> FB
  FB --> F

  F --> TR
  F --> ARR[array-suffix?]
  F --> SZ[string-size?]
  F --> DEF[default-clause?]
  F --> FS[field-suffix*]
  FS --> DISP[display string]
  FS --> PREF
  FS --> VR
  FS --> FR[fill-ref]

  SV --> SB[service-block]
  SB --> DESC[describe?]
  SB --> SD[description?]
  SB --> IN[input parameters]
  SB --> OUT[output parameters]
  SB --> THR[throws]
  SB --> IMPL[implementation code ou script]

  L --> LB[library-block]
  LB --> LD[description?]
  LB --> LF[library-function*]
  LF --> IN
  LF --> OUT

  VR --> VLIB[validators]
  VR --> VFN[function name]
  FR --> FLIB[fills]
  FR --> FFN[function name]
  FR --> FTG[target fields]

Primitivos reconhecidos em <type-ref> (quando não resolvidos como tipo nomeado):

  • uuid, string, text, number, smallint, float, money, boolean, date, timestamp, json, xml, url, image, crypto, vector, file, storage, any.

4. Geradores

4.1 TypeScriptGenerator

Por entidade ou business:

  • *.interface.ts — interface TS pura.
  • *.enum.ts — uniões de string.
  • *.schema.ts — schema Zod (validação de entrada).
  • *.model.ts — DTO + helpers de conversão snake_case ↔ camelCase.

O CRUD HTTP genérico (insert/update/delete/get/list/query OData) vive 100% no runtime, em packages/runtime/src/http. Personalizações por entidade são feitas via hooks before/after registrados em createLuhnServer({ hooks }) ou pelo PluginInstallContext.registerHook(...). Não se gera mais *.service.ts nem *.controller.ts.

Por service:

  • services/<service>.handle.ts — descritor (defineService) tipado pelo input/output declarados.
  • services/<service>.script.ts — quando implementation script, contém o handler transpilado.

Por library:

  • libraries/<library>.handle.ts.

Barrels:

  • services-scripts.ts — importa todos os .script.ts para auto-registro no boot.

Recursos adicionais:

  • síntese de relações de multitenancy via setup.multitenancy.
  • conversão automática de relações em colunas *_id no service SQL.
  • ordenação determinística dos campos (id, tenancy, demais alfabéticos).
  • exactOptionalPropertyTypes respeitado: opcionais saem com | undefined explícito.

4.2 SqlGenerator (Postgres)

Por módulo, emite generated/database/schema.sql:

  • CREATE SCHEMA IF NOT EXISTS <modulo>.
  • CREATE TABLE por entidade/business.
  • FKs para relações detectadas.
  • Tabelas filhas para arrays (records ou escalares) com parent_id + item_index.
  • Defaults SQL para literais.
  • CHECK para enums.
  • Índices automáticos em FKs.
  • Flatten de aliases estruturados em colunas.
  • Computed columns quando calculated + expressão suportada.
  • gen_random_uuid() aplicado apenas em colunas com role: 'primary_key'; relation_key em PK composta (ex.: pedido_venda_itens.pedido_venda_id) não recebe default.

Estratégias de armazenamento (storageStrategy):

| Estratégia | Quando | Resultado físico | |-------------------|--------------------------------------------|------------------| | column | escalar simples | coluna direta | | relation_fk | referência a outra entidade | *_id + FK | | child_table | array de records ou escalares | tabela filha | | embedded_record | record 1-nível sem arrays | colunas prefixadas (endereco_logradouro, endereco_cidade, ...) |

Drivers configuráveis: 'postgres' (completo) e fallback 'generic'.

4.3 OpenApiGenerator

Por módulo, emite generated/openapi/openapi.json com:

  • schemas de entities, enums, records e aliases estruturados.
  • paths CRUD por recurso (GET list, GET id, POST, PUT, PATCH, DELETE).
  • paths de services (POST /api/<modulo>/<service>).
  • componentes de input/output de service.
  • extensões x-luhn-service, x-luhn-application, x-luhn-module.
  • tags por entidade e por grupo de services do módulo.
  • exemplos OData prontos por entidade (com base em campos reais).

4.4 SchemaGenerator e CanonicalMap

  • DatabaseSchemaGenerator produz um JSON canônico das tabelas (consumido por @agility-solucoes/luhn-database no diff/migrate).
  • CanonicalMapGenerator emite objects-map.json por módulo: mapa de FQCN → metadados resolvidos. É lido em runtime para introspecção (MCP, OpenAPI servido, etc.).
  • campos com validador são emitidos com field.validate = { library, fn } no mapa canônico.
  • campos com autofill são emitidos com field.fill = { library, fn, targets } no mapa canônico.
  • campos com máscara são emitidos com field.mask = "template" no mapa canônico.

4.5 MetadataGenerator

JSON leve por módulo (application, module, lista de entities com campos básicos). Usado por dashboards.


5. Loader de Arquivos

loadApplicationInputs(dir):

  • procura arquivo com application no nome (fallback: primeiro .luhn da raiz).
  • lista recursivamente todos os .luhn.
  • extrai módulos do application por modules: [...] ou por declarações module <nome>.
  • filtra arquivos por nome de módulo no path.
  • injeta automaticamente module <pasta-raiz> em arquivos sem declaração explícita.

6. Watcher

createWatcher({ dir, onRecompile, ... }):

  • observa .luhn recursivamente.
  • debounce 100ms.
  • recompila e dispara onRecompile(result).
  • em erro de FS/compile, devolve diagnostic WATCH.

Usado pelo server-playground em --watch para recompilar + reregistrar services sem reiniciar o servidor.


7. Setup de Geração

LuhnGenerationSetup é o ponto de extensão do consumidor:

const setup: LuhnGenerationSetup = {
  database: {
    driver: 'postgres' // ou 'generic'
  },
  runtime: {
    importPath: '@agility-solucoes/luhn-runtime' // ou './src' em dev
  },
  multitenancy: {
    // opcional: ambos os blocos são injetados conforme @scope da business
    conta: { fieldName: 'conta', typeFqcn: 'plataforma.conta', keyFieldName: 'id' },
    unidade: { fieldName: 'unidade', typeFqcn: 'plataforma.unidade', keyFieldName: 'id' }
  }
};

Escopo das business

O conceito antigo de entity foi removido. Toda declaração de domínio é uma business, e o escopo de multitenancy é declarado por anotação:

| Anotação | Comportamento | | ------------------- | -------------------------------------------------------------------------------- | | @scope unidade | Padrão. Injeta conta e unidade (entidade isolada por unidade da conta). | | @scope conta | Injeta apenas conta (compartilhado entre unidades da mesma conta). | | @scope global | Não injeta nada (ex.: plataforma.conta, entidades do IAM). |

Exemplo:

@scope conta
business cliente "Clientes"
  nome: string 150
  documento: string 20

Importante: o pacote IAM não define multitenancy própria — todas as suas businesses usam @scope global. Quem decide o setup é o app hospedeiro.


8. Protocolo de Plugins do Compilador

src/plugin.ts define a forma como pacotes externos (ex.: luhn-iam) podem contribuir com diretórios .luhn adicionais para a compilação do app. O runtime, ao iniciar o servidor, agrega os luhnDirs de todos os plugins ativos e os passa ao compilador como inputs combinados.


9. Diagnósticos

Cada Diagnostic traz:

{
  code: string;        // ex.: 'SEM001', 'PARSE042', 'TS_GEN'
  severity: 'error' | 'warning';
  message: string;
  span: { filePath: string; startLine: number; startColumn: number; endLine: number; endColumn: number };
}

Categorias principais: LEX, PARSE, SEM (semantic), TS_GEN, SQL_GEN, OPENAPI_GEN, SCRIPT, WATCH.


10. Scripts de Build

pnpm --filter @luhn/compiler build   # tsc -p tsconfig.json

11. Fluxo Completo do Compilador LUHN

Esta secao resume o pipeline real do compilador, da leitura dos arquivos .luhn ate a geracao dos artefatos consumidos pelo runtime, pelo banco e pelas UIs.

11.1 Visao de ponta a ponta

Pipeline executado por compile(dir, options):

  1. loadApplicationInputs(dir) localiza o arquivo de application, descobre os modulos declarados, carrega os arquivos .luhn relevantes e injeta entradas auxiliares de types quando declaradas.
  2. parseProgram(inputs) tokeniza cada arquivo com o lexer, constroi a AST (ProgramNode) e acumula diagnosticos lexicos e sintaticos.
  3. resolveProgram(program) executa a analise semantica: resolve simbolos, imports, aliases, enum refs, records, relations, mixins, escopos, validadores, fills, mascaras e expressoes calculadas.
  4. compileInternal(...) monta o GenerationContext com resolved, diagnostics consolidados e setup opcional.
  5. Os geradores habilitados percorrem o resolved e emitem artefatos em memoria.
  6. O CompileResult retorna program, resolved, diagnostics e artifacts; a gravacao fisica dos arquivos e responsabilidade da camada consumidora do compilador.

Arquivos principais envolvidos no fluxo:

  • src/compiler.ts - ponto de entrada assíncrono.
  • src/file-loader.ts - descoberta e carga dos arquivos da aplicacao.
  • src/lexer.ts - tokenizacao e controle de INDENT/DEDENT.
  • src/parser.ts - AST e regras sintaticas.
  • src/semantic.ts - resolucao semantica e validacoes estruturais.
  • src/compiler-internal.ts - orquestracao da compilacao e dos geradores.
  • src/generator/* - emissao dos artefatos finais.

11.2 Fases detalhadas

Fase 1. Descoberta e carga de arquivos

Responsavel: loadApplicationInputs(dir).

O loader:

  • encontra o arquivo principal da aplicacao;
  • extrai os modulos declarados em application.modules ou nos blocos module;
  • lista recursivamente todos os .luhn do diretorio;
  • filtra apenas os arquivos relevantes para os modulos ativos;
  • carrega arquivos auxiliares declarados em types "...";
  • devolve SourceFileInput[] com path relativo e text UTF-8.

Essa fase define o universo de entrada da compilacao. Se um arquivo nao entrar aqui, ele nao participa da AST nem da geracao.

Fase 2. Analise lexica

Responsavel: tokenize(filePath, source) em src/lexer.ts.

O lexer:

  • normaliza quebras de linha;
  • converte texto em tokens (Identifier, String, Number, palavras reservadas e simbolos);
  • controla blocos por indentacao com tokens INDENT e DEDENT;
  • captura ScriptBody apos implementation script;
  • gera diagnosticos como string nao terminada, caractere inesperado e uso invalido de {/}.

Resultado da fase:

  • stream de tokens por arquivo;
  • diagnosticos LEX*.

Fase 3. Analise sintatica

Responsavel: parseProgram(inputs) em src/parser.ts.

O parser:

  • consome os tokens produzidos pelo lexer;
  • monta a AST bruta da linguagem (ProgramNode);
  • reconhece declaracoes top-level como application, module, import, enum, type, record, business, configuration, mixin, service e library;
  • preserva spans para diagnosticos e tooling;
  • interpreta campos, parametros, annotations, defaults, arrays, calculados, validators, fills, mascaras e implementation script.

Resultado da fase:

  • AST bruta (program);
  • diagnosticos PARSE*.

Fase 4. Analise semantica e resolucao

Responsavel: resolveProgram(program) em src/semantic.ts.

A analise semantica transforma a AST bruta em um modelo resolvido e consistente. Ela:

  • resolve imports e simbolos referenciados;
  • valida a unicidade e a coerencia de application e module;
  • detecta ciclos entre modulos;
  • normaliza tipos primitivos e aliases;
  • expande e valida mixins;
  • identifica relacoes entre entidades;
  • resolve @scope e regras de multitenancy;
  • valida defaults, enums e tipos de campo;
  • resolve validators.<fn> e fills.<fn> contra library.function;
  • propaga metadata como validate, fill, mask e visibleWhen;
  • resolve expressoes calculadas e funcoes agregadoras suportadas.

Resultado da fase:

  • resolved (ResolvedProgram);
  • diagnosticos SEM*;
  • IR pronta para geracao.

Fase 5. Montagem do contexto de geracao

Responsavel: compileInternal(inputs, options) em src/compiler-internal.ts.

Nessa fase o compilador:

  • combina diagnosticos do parser com diagnosticos do semantic;
  • injeta setup opcional (database, runtime, multitenancy);
  • decide quais geradores vao rodar com base em CompileOptions.

Selecao atual de geradores:

  • TypeScriptGenerator
  • OpenApiGenerator
  • DatabaseSchemaGenerator (habilitado quando databaseSchema !== false ou sql)
  • MetadataGenerator
  • CanonicalMapGenerator

Fase 6. Geracao de artefatos

Cada gerador recebe o mesmo GenerationContext e produz GeneratedArtifact[].

Artefatos principais:

  • TypeScript: interfaces, enums, schemas Zod, models, handles de services e libraries.
  • OpenAPI: openapi.json por modulo.
  • Database schema: JSON canonico do banco.
  • SQL/Postgres: DDL derivada do modelo resolvido.
  • Metadata: resumo leve por modulo.
  • Canonical map: objects-map.json para introspeccao e runtime.

Observacao importante:

  • DatabaseSchemaGenerator reaproveita o mapa canonico para consolidar objetos, enums e relacionamentos antes de montar a visao do banco.
  • o compilador monta os artefatos em memoria; quem grava em disco ou publica no runtime e a camada chamadora.

11.3 Palavras reservadas da linguagem

As palavras reservadas abaixo sao reconhecidas diretamente pelo lexer atual (KEYWORDS em src/lexer.ts). Elas nao devem ser usadas como identificadores soltos quando o contexto esperar nome de simbolo.

Top-level e estrutura:

  • application
  • module
  • import
  • enum
  • type
  • record
  • business
  • configuration
  • mixin
  • service
  • library
  • function

Metadados e organizacao:

  • title
  • owner
  • enabled
  • depends
  • on
  • as
  • description
  • describe
  • version
  • database
  • modules
  • types

Campos, parametros e comportamento:

  • preferred
  • calculated
  • input
  • output
  • throws
  • implementation
  • code
  • script

Literais booleanos:

  • true
  • false

Observacoes praticas:

  • validators e fills nao aparecem como palavras reservadas do lexer; elas entram como identificadores qualificados interpretados pelo parser/semantic.
  • funcoes de calculo como sum, avg, min, max e coalesce tambem nao sao keywords lexicas; sao reconhecidas no parse de expressoes calculadas.

11.4 Diagrama do fluxo completo

flowchart TD
  A[Diretorio da aplicacao LUHN] --> B[loadApplicationInputs]
  B --> C[SourceFileInput[]]
  C --> D[tokenize em src/lexer.ts]
  D --> E[Tokens + diagnostics LEX]
  E --> F[parseProgram em src/parser.ts]
  F --> G[ProgramNode AST + diagnostics PARSE]
  G --> H[resolveProgram em src/semantic.ts]
  H --> I[ResolvedProgram + diagnostics SEM]
  I --> J[compileInternal]
  J --> K[GenerationContext]
  K --> L[TypeScriptGenerator]
  K --> M[OpenApiGenerator]
  K --> N[DatabaseSchemaGenerator]
  K --> O[MetadataGenerator]
  K --> P[CanonicalMapGenerator]
  L --> Q[Artefatos TS]
  M --> R[openapi.json]
  N --> S[schema.json e/ou SQL]
  O --> T[metadata.json]
  P --> U[objects-map.json]
  Q --> V[CompileResult.artifacts]
  R --> V
  S --> V
  T --> V
  U --> V

11.5 Fluxo resumido de analise ate geracao de codigo

Em uma frase: o compilador carrega os arquivos da aplicacao, tokeniza o texto, monta a AST, resolve semanticamente o dominio, consolida diagnosticos e so entao gera artefatos tipados e canônicos para TypeScript, OpenAPI, banco, metadata e runtime.

Resumo operacional:

  1. Entrada: arquivos .luhn e CompileOptions.
  2. Analise lexica: tokens e erros de formato.
  3. Analise sintatica: AST completa.
  4. Analise semantica: resolucao de simbolos, tipos, relations, mixins e regras.
  5. IR resolvida: estrutura pronta para geracao.
  6. Geracao: artefatos derivados por modulo e por entidade.
  7. Saida: CompileResult com AST, IR resolvida, diagnosticos e artefatos.

Como main: "src/index.ts" e type: "commonjs", o build com tsc produz .js+.d.ts ao lado do .ts (sem outDir). Em dev, os consumidores podem importar direto o .ts via ts-node.


11. Limitações Atuais

  • Não há REPL nem CLI próprio (use apps/cli ou os scripts do apps/playground).
  • A mini-linguagem de implementation script é intencionalmente restrita — para lógicas complexas, use implementation code (handler TS).
  • Generators emitem para Postgres; outros bancos caem no fallback 'generic' (sem FK/CHECK/índices).
  • Watcher é por polling do fs.watch nativo do Node — pode perder eventos em filesystems remotos.