@hamak/smart-data-dico 1.9.2 → 1.10.0

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.
@@ -10082,6 +10082,65 @@ function validateDerivedTypes(types) {
10082
10082
  }
10083
10083
  __name(validateDerivedTypes, "validateDerivedTypes");
10084
10084
 
10085
+ // backend/src/models/ormVocabulary.ts
10086
+ var ORM_VOCABULARY = {
10087
+ entity: [
10088
+ { key: "orm.package", kind: "string", label: "Java package", mapsTo: "class package (FQN)" },
10089
+ { key: "orm.className", kind: "string", label: "Class name", mapsTo: "class name override" },
10090
+ { key: "orm.embeddable", kind: "flag", label: "Embeddable", mapsTo: "@Embeddable" },
10091
+ { key: "orm.mappedSuperclass", kind: "flag", label: "Mapped superclass", mapsTo: "@MappedSuperclass" },
10092
+ { key: "orm.extends", kind: "entityRef", label: "Extends (supertype)", mapsTo: "inheritance \u2014 extends parent entity" },
10093
+ { key: "orm.inheritanceStrategy", kind: "enum", values: ["SINGLE_TABLE", "JOINED", "TABLE_PER_CLASS"], label: "Inheritance strategy", mapsTo: "@Inheritance (on root)" },
10094
+ { key: "orm.discriminatorColumn", kind: "string", label: "Discriminator column", mapsTo: "@DiscriminatorColumn (root)" },
10095
+ { key: "orm.discriminatorValue", kind: "string", label: "Discriminator value", mapsTo: "@DiscriminatorValue (subclass)" },
10096
+ { key: "orm.idClass", kind: "string", label: "Id class", mapsTo: "@IdClass (composite key)" },
10097
+ { key: "orm.embeddedId", kind: "string", label: "Embedded id", mapsTo: "@EmbeddedId (composite key)" }
10098
+ ],
10099
+ attribute: [
10100
+ { key: "orm.javaType", kind: "string", label: "Java type", mapsTo: "field Java type override" },
10101
+ { key: "orm.generatedValue", kind: "enum", values: ["IDENTITY", "SEQUENCE", "TABLE", "UUID", "AUTO", "NONE"], label: "Generated value", mapsTo: "@GeneratedValue" },
10102
+ { key: "orm.sequenceName", kind: "string", label: "Sequence name", mapsTo: "@SequenceGenerator(name)" },
10103
+ { key: "orm.allocationSize", kind: "int", label: "Allocation size", mapsTo: "@SequenceGenerator(allocationSize)" },
10104
+ { key: "orm.enumerated", kind: "enum", values: ["STRING", "ORDINAL"], label: "Enumerated", mapsTo: "@Enumerated (enum attrs)" },
10105
+ { key: "orm.enumType", kind: "string", label: "Enum type", mapsTo: "generated Java enum class" },
10106
+ { key: "orm.version", kind: "flag", label: "Version", mapsTo: "@Version" },
10107
+ { key: "orm.transient", kind: "flag", label: "Transient", mapsTo: "@Transient" },
10108
+ { key: "orm.lob", kind: "flag", label: "Lob", mapsTo: "@Lob" },
10109
+ { key: "orm.temporal", kind: "enum", values: ["DATE", "TIME", "TIMESTAMP"], label: "Temporal", mapsTo: "@Temporal" },
10110
+ { key: "orm.converter", kind: "string", label: "Converter", mapsTo: "@Convert(converter)" },
10111
+ { key: "orm.elementCollection", kind: "flag", label: "Element collection", mapsTo: "@ElementCollection" },
10112
+ { key: "orm.embedded", kind: "flag", label: "Embedded", mapsTo: "@Embedded" }
10113
+ ],
10114
+ relationship: [
10115
+ { key: "orm.fetch", kind: "enum", values: ["LAZY", "EAGER"], label: "Fetch", mapsTo: "fetch type" },
10116
+ { key: "orm.cascade", kind: "enumList", values: ["ALL", "PERSIST", "MERGE", "REMOVE", "REFRESH", "DETACH"], label: "Cascade", mapsTo: "cascade types" },
10117
+ { key: "orm.orphanRemoval", kind: "flag", label: "Orphan removal", mapsTo: "orphanRemoval" },
10118
+ { key: "orm.optional", kind: "flag", label: "Optional", mapsTo: "optional" },
10119
+ { key: "orm.mappedBy", kind: "string", label: "Mapped by", mapsTo: "mappedBy (inverse side)" },
10120
+ { key: "orm.owningEnd", kind: "string", label: "Owning end", mapsTo: "owning side role" },
10121
+ { key: "orm.joinTable", kind: "string", label: "Join table", mapsTo: "@JoinTable(name) for many-to-many" },
10122
+ { key: "orm.joinColumns", kind: "string", label: "Join columns", mapsTo: "@JoinTable joinColumns" },
10123
+ { key: "orm.inverseJoinColumns", kind: "string", label: "Inverse join columns", mapsTo: "@JoinTable inverseJoinColumns" }
10124
+ ]
10125
+ };
10126
+ var ORM_KEYS = {
10127
+ entity: new Set(ORM_VOCABULARY.entity.map((d) => d.key)),
10128
+ attribute: new Set(ORM_VOCABULARY.attribute.map((d) => d.key)),
10129
+ relationship: new Set(ORM_VOCABULARY.relationship.map((d) => d.key))
10130
+ };
10131
+ var allDefs = [
10132
+ ...ORM_VOCABULARY.entity,
10133
+ ...ORM_VOCABULARY.attribute,
10134
+ ...ORM_VOCABULARY.relationship
10135
+ ];
10136
+ var ORM_ENUM_VALUES = Object.fromEntries(
10137
+ allDefs.filter((d) => d.kind === "enum" && d.values).map((d) => [d.key, d.values])
10138
+ );
10139
+ var ORM_CASCADE_VALUES = allDefs.find((d) => d.key === "orm.cascade")?.values ?? [];
10140
+ var ORM_FLAG_KEYS = new Set(
10141
+ allDefs.filter((d) => d.kind === "flag").map((d) => d.key)
10142
+ );
10143
+
10085
10144
  // backend/src/scripts/validateDico.ts
10086
10145
  var WS2 = wsId("dictionaries");
10087
10146
  var Report = class {
@@ -10237,7 +10296,137 @@ function collectAttributes(attrs, acc) {
10237
10296
  }
10238
10297
  }
10239
10298
  __name(collectAttributes, "collectAttributes");
10240
- function checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, report) {
10299
+ function checkOrmMetadata(scope, metadata, ownerDesc, label, identifier, report, ctx) {
10300
+ const entries = (metadata || []).filter((m) => m.name.startsWith("orm."));
10301
+ if (entries.length === 0) return;
10302
+ const byName = new Map(entries.map((e) => [e.name, e.value]));
10303
+ for (const m of entries) {
10304
+ const key = m.name;
10305
+ if (!ORM_KEYS[scope].has(key)) {
10306
+ report.warn("orm.unknownKey", `Unknown or misplaced ORM key '${key}' on ${ownerDesc} (not a known ${scope}-level orm.* key)`, label, identifier);
10307
+ continue;
10308
+ }
10309
+ const v = m.value;
10310
+ if (key === "orm.cascade") {
10311
+ const tokens = Array.isArray(v) ? v.map(String) : String(v).split(",").map((s) => s.trim()).filter(Boolean);
10312
+ for (const tok of tokens) {
10313
+ if (!ORM_CASCADE_VALUES.includes(tok)) {
10314
+ report.error("orm.value", `ORM '${key}' on ${ownerDesc}: '${tok}' is not a valid cascade type (${ORM_CASCADE_VALUES.join("|")})`, label, identifier);
10315
+ }
10316
+ }
10317
+ } else if (ORM_ENUM_VALUES[key]) {
10318
+ if (!ORM_ENUM_VALUES[key].includes(String(v))) {
10319
+ report.error("orm.value", `ORM '${key}' on ${ownerDesc}: '${v}' is not one of ${ORM_ENUM_VALUES[key].join("|")}`, label, identifier);
10320
+ }
10321
+ } else if (ORM_FLAG_KEYS.has(key) && typeof v !== "boolean") {
10322
+ report.warn("orm.flag", `ORM flag '${key}' on ${ownerDesc} should be a boolean, got '${v}'`, label, identifier);
10323
+ }
10324
+ }
10325
+ if (scope === "entity" && byName.has("orm.extends")) {
10326
+ const ref = String(byName.get("orm.extends"));
10327
+ if (ref && !ctx.globalEntityUuids.has(ref) && !ctx.globalEntityNames.has(ref)) {
10328
+ report.error("orm.reference", `ORM 'orm.extends' on ${ownerDesc} references '${ref}', which is not an entity in the project`, label, identifier);
10329
+ }
10330
+ }
10331
+ if (scope === "attribute" && byName.get("orm.version") === true && byName.get("orm.transient") === true) {
10332
+ report.error("orm.conflict", `${ownerDesc} marks both orm.version and orm.transient`, label, identifier);
10333
+ }
10334
+ if (scope === "entity" && byName.get("orm.embeddable") === true && byName.has("orm.extends")) {
10335
+ report.error("orm.conflict", `${ownerDesc} is orm.embeddable but also declares orm.extends (an @Embeddable cannot extend an @Entity)`, label, identifier);
10336
+ }
10337
+ if (scope === "attribute" && byName.has("orm.enumerated") && ctx.attrType && ctx.attrType !== "enum") {
10338
+ report.warn("orm.enumerated", `orm.enumerated on ${ownerDesc} but the attribute type is '${ctx.attrType}', not 'enum'`, label, identifier);
10339
+ }
10340
+ }
10341
+ __name(checkOrmMetadata, "checkOrmMetadata");
10342
+ function checkOrmInheritance(models, report) {
10343
+ const byUuid = /* @__PURE__ */ new Map();
10344
+ const byName = /* @__PURE__ */ new Map();
10345
+ for (const { pkg, model } of models) {
10346
+ const fileOf = model.ownership;
10347
+ for (const e of model.entities) {
10348
+ const label = fileOf.entityByName.get(e.name) || fileOf.entityByUuid.get(e.uuid) || pkg;
10349
+ const node = { entity: e, label };
10350
+ if (e.uuid) byUuid.set(e.uuid, node);
10351
+ if (e.name) byName.set(e.name, node);
10352
+ }
10353
+ }
10354
+ const meta = /* @__PURE__ */ __name((e, key) => (e.metadata || []).find((m) => m.name === key)?.value, "meta");
10355
+ const idOf = /* @__PURE__ */ __name((e) => e.uuid || e.name, "idOf");
10356
+ const parentOf = /* @__PURE__ */ __name((e) => {
10357
+ const ref = meta(e, "orm.extends");
10358
+ if (ref === void 0 || ref === null || ref === "") return void 0;
10359
+ const s = String(ref);
10360
+ return byUuid.get(s) || byName.get(s);
10361
+ }, "parentOf");
10362
+ const reportedCycle = /* @__PURE__ */ new Set();
10363
+ for (const node of /* @__PURE__ */ new Set([...byUuid.values(), ...byName.values()])) {
10364
+ const e = node.entity;
10365
+ if (meta(e, "orm.extends") === void 0) continue;
10366
+ const path4 = /* @__PURE__ */ new Set([idOf(e)]);
10367
+ let cur = e;
10368
+ let steps = 0;
10369
+ while (cur && steps++ < 100) {
10370
+ const p = parentOf(cur);
10371
+ if (!p) break;
10372
+ const pid = idOf(p.entity);
10373
+ if (path4.has(pid)) {
10374
+ if (!reportedCycle.has(pid)) {
10375
+ reportedCycle.add(pid);
10376
+ report.error(
10377
+ "orm.inheritanceCycle",
10378
+ `ORM inheritance cycle via orm.extends through entity '${p.entity.name}'`,
10379
+ p.label,
10380
+ p.entity.uuid
10381
+ );
10382
+ }
10383
+ break;
10384
+ }
10385
+ path4.add(pid);
10386
+ cur = p.entity;
10387
+ }
10388
+ if (meta(e, "orm.inheritanceStrategy") !== void 0) {
10389
+ report.warn(
10390
+ "orm.inheritance",
10391
+ `Entity '${e.name}' declares both orm.extends and orm.inheritanceStrategy \u2014 the strategy belongs on the root supertype`,
10392
+ node.label,
10393
+ e.uuid
10394
+ );
10395
+ }
10396
+ const parent = parentOf(e);
10397
+ if (parent && meta(parent.entity, "orm.embeddable") === true) {
10398
+ report.error(
10399
+ "orm.conflict",
10400
+ `Entity '${e.name}' orm.extends '${parent.entity.name}', which is orm.embeddable (an @Embeddable cannot be a superclass)`,
10401
+ node.label,
10402
+ e.uuid
10403
+ );
10404
+ }
10405
+ if (meta(e, "orm.discriminatorValue") !== void 0) {
10406
+ let hasDisc = false;
10407
+ let a = parentOf(e);
10408
+ const guard = /* @__PURE__ */ new Set([idOf(e)]);
10409
+ while (a && !guard.has(idOf(a.entity))) {
10410
+ guard.add(idOf(a.entity));
10411
+ if (meta(a.entity, "orm.discriminatorColumn") !== void 0) {
10412
+ hasDisc = true;
10413
+ break;
10414
+ }
10415
+ a = parentOf(a.entity);
10416
+ }
10417
+ if (!hasDisc) {
10418
+ report.warn(
10419
+ "orm.inheritance",
10420
+ `Entity '${e.name}' sets orm.discriminatorValue but no ancestor declares orm.discriminatorColumn`,
10421
+ node.label,
10422
+ e.uuid
10423
+ );
10424
+ }
10425
+ }
10426
+ }
10427
+ }
10428
+ __name(checkOrmInheritance, "checkOrmInheritance");
10429
+ function checkPackageModel(pkg, model, globalEntityUuids, globalEntityNames, globalAttrUuids, report) {
10241
10430
  const fileOf = model.ownership;
10242
10431
  for (const entity of model.entities) {
10243
10432
  const label = fileOf.entityByName.get(entity.name) || fileOf.entityByUuid.get(entity.uuid) || pkg;
@@ -10271,6 +10460,11 @@ function checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, repor
10271
10460
  globalAttrUuids.set(attr.uuid, `${label} (entity '${entity.name}', attribute '${attr.name}')`);
10272
10461
  }
10273
10462
  }
10463
+ const ormCtx = { globalEntityUuids, globalEntityNames };
10464
+ checkOrmMetadata("entity", entity.metadata, `entity '${entity.name}'`, label, entity.uuid, report, ormCtx);
10465
+ for (const attr of allAttrs) {
10466
+ checkOrmMetadata("attribute", attr.metadata, `attribute '${attr.name}' on entity '${entity.name}'`, label, attr.uuid, report, { ...ormCtx, attrType: String(attr.type) });
10467
+ }
10274
10468
  }
10275
10469
  for (const rel of model.relationships) {
10276
10470
  const label = fileOf.relationshipByUuid.get(rel.uuid) || pkg;
@@ -10303,6 +10497,7 @@ function checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, repor
10303
10497
  );
10304
10498
  }
10305
10499
  }
10500
+ checkOrmMetadata("relationship", rel.metadata, `relationship '${rel.uuid}'`, label, rel.uuid, report, { globalEntityUuids, globalEntityNames });
10306
10501
  }
10307
10502
  const allRules = [
10308
10503
  ...model.rules,
@@ -10440,6 +10635,29 @@ function checkFlowSteps(flow, actionUuid, actionName, label, actionUuids, report
10440
10635
  }
10441
10636
  }
10442
10637
  __name(checkFlowSteps, "checkFlowSteps");
10638
+ function countByCode(findings) {
10639
+ const counts = /* @__PURE__ */ new Map();
10640
+ for (const finding of findings) {
10641
+ counts.set(finding.code, (counts.get(finding.code) ?? 0) + 1);
10642
+ }
10643
+ return [...counts.entries()].sort(([codeA, countA], [codeB, countB]) => {
10644
+ if (countA !== countB) return countB - countA;
10645
+ return codeA.localeCompare(codeB);
10646
+ });
10647
+ }
10648
+ __name(countByCode, "countByCode");
10649
+ function printCategoryStats(title, findings) {
10650
+ console.error(`${title}:`);
10651
+ const counts = countByCode(findings);
10652
+ if (counts.length === 0) {
10653
+ console.error(" none");
10654
+ return;
10655
+ }
10656
+ for (const [code, count] of counts) {
10657
+ console.error(` ${code}: ${count}`);
10658
+ }
10659
+ }
10660
+ __name(printCategoryStats, "printCategoryStats");
10443
10661
  function printReport(report, root) {
10444
10662
  const errors = report.findings.filter((f) => f.severity === "error");
10445
10663
  const warnings = report.findings.filter((f) => f.severity === "warning");
@@ -10463,11 +10681,13 @@ Validating data-dictionary project: ${root}
10463
10681
  console.error("");
10464
10682
  }
10465
10683
  if (errors.length === 0 && warnings.length === 0) {
10466
- console.error("OK \u2014 no problems found.\n");
10467
- } else {
10468
- console.error(`Summary: ${errors.length} error(s), ${warnings.length} warning(s).
10469
- `);
10684
+ console.error("OK \u2014 no problems found.");
10470
10685
  }
10686
+ const status = errors.length > 0 ? "FAILED" : "PASSED";
10687
+ console.error(`Summary: ${status} \u2014 ${errors.length} error(s), ${warnings.length} warning(s).`);
10688
+ printCategoryStats("Error categories", errors);
10689
+ printCategoryStats("Warning categories", warnings);
10690
+ console.error("");
10471
10691
  }
10472
10692
  __name(printReport, "printReport");
10473
10693
  var USAGE = `Validate a Smart Data Dictionary project folder.
@@ -10520,12 +10740,17 @@ async function validateProject(root, report) {
10520
10740
  report.warn("loader.unavailable", `App loader cross-check failed: ${e.message}`, root);
10521
10741
  }
10522
10742
  const globalEntityUuids = /* @__PURE__ */ new Set();
10743
+ const globalEntityNames = /* @__PURE__ */ new Set();
10523
10744
  for (const { model } of models) {
10524
- for (const e of model.entities) if (e.uuid) globalEntityUuids.add(e.uuid);
10745
+ for (const e of model.entities) {
10746
+ if (e.uuid) globalEntityUuids.add(e.uuid);
10747
+ if (e.name) globalEntityNames.add(e.name);
10748
+ }
10525
10749
  }
10526
10750
  for (const { pkg, model } of models) {
10527
- checkPackageModel(pkg, model, globalEntityUuids, globalAttrUuids, report);
10751
+ checkPackageModel(pkg, model, globalEntityUuids, globalEntityNames, globalAttrUuids, report);
10528
10752
  }
10753
+ checkOrmInheritance(models, report);
10529
10754
  }
10530
10755
  __name(validateProject, "validateProject");
10531
10756
  async function main() {
@@ -10554,6 +10779,7 @@ if (invokedDirectly) {
10554
10779
  }
10555
10780
  export {
10556
10781
  Report,
10782
+ printReport,
10557
10783
  resolveProjectDir,
10558
10784
  validateProject
10559
10785
  };
package/bin/cli.js CHANGED
@@ -9,6 +9,12 @@ import { spawn, spawnSync } from 'child_process';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  const PKG_ROOT = join(__dirname, '..');
12
+ let packageVersion = '';
13
+ try {
14
+ packageVersion = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf-8')).version || '';
15
+ } catch {
16
+ packageVersion = '';
17
+ }
12
18
 
13
19
  // Parse CLI arguments
14
20
  const args = process.argv.slice(2);
@@ -169,6 +175,7 @@ function startServer(dir) {
169
175
  NODE_ENV: 'production',
170
176
  PROFILE: process.env.PROFILE || 'local',
171
177
  DATA_DIR: dir,
178
+ SDD_VERSION: packageVersion,
172
179
  SDD_FRONTEND_DIST: frontendDist,
173
180
  SDD_MANAGED: '1',
174
181
  },