@m3r/sdk 0.5.0

Padrões de desenvolvimento

Como fatiar a extensão por pastas — tabelas, telas, nav, relatórios e IPs — e ligar tudo no index.ts.

O Core não lê a sua árvore de pastas. Ele lê o que o activate declara. A pasta existe para você não misturar venda com estoque num único arquivo.

O m3r.json → entry aponta para src/index.ts. Esse arquivo só orquestra. Cada domínio registra o próprio contrato.

Árvore

my-extension
m3r.json
package.json
src
index.ts
shared
pedido.ts
vendas
register.ts
nav.ts
ips.ts
events.ts
tabelas
auditoria.ts
lote.ts
telas
auditoria.ts
auditoria-novo.ts
lote.ts
relatorios
periodo.ts
lote-posicao.ts

compras/ só entra quando houver tabela, tela ou IP daquele domínio. Não crie pasta vazia.

Referência viva: sample 0.6.1 (examples/sample-extension).

O que vai em cada pasta

PastaContémChama
tabelas/um arquivo por tabelactx.ext.tables.ensure
telas/um arquivo por codectx.ext.screens.define
relatorios/um arquivo por codectx.ext.reports.define
nav.tsitens de menu daquele domínioctx.ext.nav.define
ips.tshandlers entryPoints.onctx.subscriptions.add
events.tshandlers events.onctx.subscriptions.add
register.tsordem do domínioas funções acima
shared/helper sem ensure/definerecords, payload
index.tsdefineExtensionregisterVendas, registerCompras, …

Um arquivo de tabela não define tela. Uma tela não registra IP. IP que grava linha importa o helper de shared/, não o ensure.

Ordem no register

tabelas.ensure  →  telas.define  →  nav.define  →  reports.define  →  IPs  →  eventos

Tela aponta para table que já passou pelo ensure. Nav aponta para tela ou relatório já definidos. IP AFTER_SAVE pode gravar record — a tabela já existe.

src/vendas/register.ts
export async function registerVendas(ctx: ExtensionContext): Promise<void> {
  await ensureAuditoriaTable(ctx);
  await ensureLoteTable(ctx);
  await defineAuditoriaScreens(ctx);
  await defineVendasNav(ctx);
  await defineAuditoriaPeriodoReport(ctx);
  registerVendasEntryPoints(ctx);
  registerVendasEvents(ctx);
}

Como o index.ts inicia

src/index.ts
import { defineExtension } from "@m3r/sdk";
import { registerVendas } from "./vendas/register";
// import { registerCompras } from "./compras/register";

export default defineExtension({
  async activate(ctx) {
    await registerVendas(ctx);
    // await registerCompras(ctx);
  },
  async deactivate(ctx) {
    ctx.logger.info("stop");
  }
});

Não registre entryPoints.on no index. Não chame tables.ensure no index. O deactivate não desfaz ensure — só encerra o processo.

m3r.json lista todos os tables[], screens[], nav[], reports[] da extensão, de todos os domínios. O validate varre src/**/*.ts.

Nomes

CoisaPadrãoExemplo
Pasta de domínioslug do módulo de negóciovendas, compras, estoque, financeiro, rh
Tabela name[a-z][a-z0-9_]{1,47}demo_auditoria, demo_lote
Colunamesmo slugpedido_codigo
Tela / nav / relatório code[a-z][a-z0-9_.-]{1,62}demo.auditoria.novo
Prefixoid da extensão ou domíniodemo.

Não use o código físico FAX#### como name da tabela. name é lógico. O físico vem no retorno do ensure.

Tabela documento vs física

document (padrão)physical
OndeJSONB em FADEV07Postgres FAX0001–FAX9999
Quem criaCore, sem DDL seuCore (CREATE TABLE), nunca a extensão
physicalName—opcional; omitido = Core atribui e devolve
uniqueKeyso Core checa as linhas JSONBUNIQUE de verdade no Postgres
Quando usarcadastro leve, auditoriavolume, unicidade, relatório denso

Colunas reservadas (não declare): id, company_id, extension_id, created_by, created_at, updated_at, delete_date, table_id.

remove é soft delete. Isolamento: companyId + extensionId. Ambiente FADEV02 não separa dado.

Evolução só adiciona coluna. Sem drop, rename, troca de tipo ou de storage.

Detalhe da API: ctx.ext.

Telas

kindHostUso
Page/apps/extensions/{code}lista + formulário
Modaloverlay a partir de venda ou actionbind SdkFormBind

actions[].opens: openScreen:, openModal:, openReport:. Sem prefixo = tela.

Depois do mount: IP.EXT.SCREEN.ON_LOAD — setField / hideField / disableField. Não nega o load.

placement.parent ou placement.section: Extensions. Nunca os dois.

SdkMenuParent (const → menuKey do ERP):

ConstmenuKey
Vendasvendas
Comissoescommissions
Devolucoesreturns
Crmcrm
Relatoriosreports
Cadastroscadastros
Operacoesoperacoes
Producaoproducao
EntradaDocumentosentrada-documentos
Movimentacaomovimentacao
Inventoryinventory
PurchaseIntelligencepurchase-intelligence
FiscalOperationalfiscal-operational
NfeMonitornfe-monitor
Spedsped
RhGestaorh-gestao
RhFolharh-folha
Lancamentoslancamentos
RecuperacaoCreditorecuperacao-credito

Typo (parent: "Vendas") não compile. Use SdkMenuParent.Vendas.

Novo domínio

  1. Pasta src/compras/ com tabelas/, telas/, register.ts.
  2. registerCompras na mesma ordem (tabela → tela → nav → …).
  3. Uma linha no activate: await registerCompras(ctx).
  4. Incluir os novos ids no m3r.json.
  5. m3r validate.

ctx.m3r.purchase ainda é planned. Pasta compras/ hoje serve para tabela/tela/IP da extensão, não para chamar o módulo de compras do Core.