@mostajs/orm-lint
v0.1.0
Published
SQL DDL/migration linter for @mostajs/orm — generates annotated DDL from EntitySchema, lints via Atlas + house rules, remaps findings back to EntitySchema coordinates
Maintainers
Readme
@mostajs/orm-lint
Auteur : Dr Hamid MADANI [email protected] · Licence : AGPL-3.0-or-later
Statut : ✅ 0.1.0 — implémenté, 26 tests verts (via @mostajs/mjs-unit).
Linter SQL pour @mostajs/orm. Là où @mostajs/orm/validator
analyse l'EntitySchema (règles conceptuelles), ce module passe par le SQL généré
pour attraper les anti-patterns invisibles au niveau abstrait (mots réservés, FK sans
index, ADD COLUMN NOT NULL sans DEFAULT, identifiants trop longs…), via des
règles maison et Atlas (optionnel), puis remappe chaque finding sur les
coordonnées (schema, field).
Principe : réutilisation, pas réinvention
Le module ne réimplémente pas le SQL. Il réutilise l'instance de dialecte de l'ORM
(@mostajs/orm/dialects, sous-export né en ORM 2.11.0) et ses méthodes publiques
generateCreateTable / generateIndexes — le DDL linté est exactement celui que
l'ORM produit, pour les 17 dialectes SQL. La plus-value ajoutée ici : annotations,
règles maison, Atlas, remapping. Sélection du dialecte par registre piloté .env
(pas de switch).
EntitySchema[]
│ (réutilise @mostajs/orm/dialects → createDialect(type), sans connexion)
▼
DDL/Migration exact de l'ORM ──annoté──► -- @mostajs-orm:schema=…,field=…
│
├─► règles maison (6) ─┐
├─► Atlas schema lint (opt.) ─┤► findings bruts ──remap (ligne→schema/field)──► Finding[]
└─────────────────────────────┘Installation
npm i -D @mostajs/orm-lint @mostajs/orm # @mostajs/orm >= 2.11.0
brew install ariga/tap/atlas # optionnel (sinon fallback règles maison)Démarrage rapide
import { validateSqlGenerated } from "@mostajs/orm-lint";
const schemas = [
{ name: "Project", collection: "projects",
fields: { title: { type: "string", required: true } },
relations: {}, indexes: [], timestamps: true },
{ name: "Registration", collection: "registrations",
fields: { code: { type: "string", required: true } },
relations: { project: { target: "Project", type: "many-to-one", required: true } },
indexes: [], timestamps: true },
];
const report = await validateSqlGenerated(schemas, { dialect: "postgres", tools: ["house"] });
for (const f of report.findings) {
console.log(`[${f.severity}] ${f.ruleId} ${f.location.schema}.${f.location.field} — ${f.message}`);
}
// [warning] SQL-HOUSE-FK-WITHOUT-INDEX Registration.project — colonne FK "project" sans indexAPI
| Export | Rôle |
|---|---|
| validateSqlGenerated(schemas, options?) | pipeline complet → Promise<SqlLintReport> |
| generateDdlForLint(schemas, ld, {annotate}) | DDL annoté (réutilise l'ORM) |
| generateMigrationForLint(old, new, ld, {annotate}) | ALTER annotés (champs ajoutés) |
| getLintDialect(config) | dialecte de lint (instance ORM + métadonnées) |
| supportedDialects() | liste des dialectes SQL |
| runHouseRules(sql, ld, fkByTable, enabled?) | règles maison sur du SQL arbitraire |
| parseAnnotations(sql) / remapToFinding(raw, map) | annotations & remapping |
| isAtlasAvailable(bin?) / runAtlasLint(...) / parseAtlasOutput(json) | Atlas |
| loadConfig(dialectOverride?) | config résolue depuis .env |
SqlLintOptions
{ dialect?, scope?: "ddl"|"migration"|"both", oldSchemas?, tools?: ("house"|"atlas")[],
atlasBinary?, atlasMissingBehavior?: "graceful"|"strict", houseRules?, ignore? }Tout champ absent est résolu depuis l'environnement (.env).
SqlLintReport
{ schemaCount, findings: Finding[], toolsRun, atlasAvailable, durationMs,
generatedDdl?, generatedMigration? }Finding = { ruleId, severity, message, location:{line?,schema?,field?}, suggestion, fixable }
(structurellement compatible avec Finding de @mostajs/orm/validator).
Règles maison
| Code (SQL-HOUSE-…) | Sévérité | Détecte | Exemple TP |
|---|---|---|---|
| RESERVED-WORDS | warning | identifiant non quoté réservé | CREATE TABLE t (user TEXT…) |
| MISSING-PK | error | table sans PRIMARY KEY | CREATE TABLE t (a TEXT) |
| FK-WITHOUT-INDEX | warning | colonne FK sans index | relation project non indexée |
| COMPOSITE-INDEX-PK-FIRST | info | index composite préfixé par la PK seule | INDEX(id, code) avec PK id |
| NOT-NULL-WITHOUT-DEFAULT-IN-ALTER | error | ADD COLUMN … NOT NULL sans DEFAULT | lock ACCESS EXCLUSIVE en prod |
| IDENTIFIER-TOO-LONG | warning | identifiant > limite du dialecte | colonne de 70 car. en Postgres (63) |
Atlas (si installé) : ses diagnostics AR* sont remappés sous SQL-ATLAS-AR* ; sinon
fallback graceful. atlasMissingBehavior: "strict" lève si Atlas est absent.
Configuration .env (via @mostajs/config — jamais en dur)
| Clé | Rôle |
|---|---|
| ORM_LINT_DIALECT | dialecte (sinon DB_DIALECT, sinon postgres) |
| ORM_LINT_TOOLS | house,atlas |
| ORM_LINT_ATLAS_BINARY / _ATLAS_MISSING | binaire Atlas / graceful|strict |
| ORM_LINT_IGNORE / _HOUSE_RULES | règles ignorées / restreintes |
| ORM_LINT_RESERVED[_<DIALECT>] | mots réservés supplémentaires |
| ORM_LINT_LIMIT_<DIALECT> | limite d'identifiant |
Cascade ${MOSTA_ENV}_${KEY} → ${KEY} → défaut. Charger le .env en amont
(node --env-file=.env ou dotenv de l'app). Priorité : options de code > .env > défauts SQL.
CLI
orm-lint examples/schemas.mjs --dialect postgres --scope both --tools house,atlas
orm-lint examples/schemas.mjs --ignore SQL-HOUSE-FK-WITHOUT-INDEX --json
orm-lint examples/schemas.mjs --ddl # imprime aussi le DDL généréLe fichier exporte schemas (ESM) ou est un tableau (.json). Sortie 1 si finding error.
Exemples & démo
node examples/demo.mjs # lint DDL : DDL annoté + findings console
node examples/migration-demo.mjs # scope migration : ADD COLUMN NOT NULL sans DEFAULT
node examples/multi-dialect-demo.mjs# même schéma sur Postgres/MySQL/Oracle (limites d'identifiant)
node examples/annotations-demo.mjs # les annotations : DDL annoté → carte → remapping
orm-lint examples/schemas.mjs # même chose en CLITous décrits dans examples/README.md.
Tests
Suite exécutée par le CLI @mostajs/mjs-unit :
npm test # typecheck → build → node node_modules/.bin/mjs-unit test-scripts/*.test.mjs26 tests : règles maison (TP/TN), générateur, annotations/remapping, Atlas, E2E multi-dialecte + .env.
Limites
- DDL généré sans exécution (l'ORM couple DDL+exécution dans
initSchema). - Dialectes non-SQL (
mongodb/firestore/redis) → exception explicite. --fixnon implémenté (findingsfixable: falseen 0.1.0).
