@omnifyjp/omnify 5.8.4 → 5.8.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/omnify",
3
- "version": "5.8.4",
3
+ "version": "5.8.6",
4
4
  "description": "Schema-driven code generation for Laravel, TypeScript, and SQL",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "zod": "^3.24.0"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@omnifyjp/omnify-darwin-arm64": "5.8.4",
40
- "@omnifyjp/omnify-darwin-x64": "5.8.4",
41
- "@omnifyjp/omnify-linux-x64": "5.8.4",
42
- "@omnifyjp/omnify-linux-arm64": "5.8.4",
43
- "@omnifyjp/omnify-win32-x64": "5.8.4"
39
+ "@omnifyjp/omnify-darwin-arm64": "5.8.6",
40
+ "@omnifyjp/omnify-darwin-x64": "5.8.6",
41
+ "@omnifyjp/omnify-linux-x64": "5.8.6",
42
+ "@omnifyjp/omnify-linux-arm64": "5.8.6",
43
+ "@omnifyjp/omnify-win32-x64": "5.8.6"
44
44
  }
45
45
  }
@@ -111,7 +111,7 @@ function generateBaseModel(name, schema, reader, config) {
111
111
  const hidden = buildHidden(properties, expandedProperties, propertyOrder);
112
112
  const appends = buildAppends(expandedProperties);
113
113
  const casts = buildCasts(properties, expandedProperties, propertyOrder, reader, config);
114
- const relations = buildRelations(name, properties, propertyOrder, modelNamespace, reader);
114
+ const relations = buildRelations(name, properties, propertyOrder, modelNamespace, reader, config);
115
115
  const accessors = buildAccessors(expandedProperties);
116
116
  const fileAccessors = hasFiles ? buildFileAccessors(properties, propertyOrder, modelNamespace) : '';
117
117
  const auditSection = buildAuditSection(options, reader, modelNamespace);
@@ -578,7 +578,51 @@ function buildCasts(properties, expandedProperties, propertyOrder, reader, confi
578
578
  return '';
579
579
  return casts.map(c => ` ${c}`).join('\n') + '\n';
580
580
  }
581
- function buildRelations(schemaName, properties, propertyOrder, modelNamespace, reader) {
581
+ /**
582
+ * Issue #98 v5.8.6: resolve the FQCN namespace of a related Model
583
+ * for inline `belongsTo / hasMany / belongsToMany / morphTo` references.
584
+ * The namespace must match the TARGET's CANONICAL CLASS LOCATION:
585
+ *
586
+ * 1. Package schemas — return the package's namespace verbatim
587
+ * (already handled by reader.resolveModelNamespace).
588
+ * 2. Pivot schemas — always nest under `Pivot/` regardless of any
589
+ * group / flat flag (matches the dedicated `Pivot/` folder
590
+ * enforced for pivot models).
591
+ * 3. flatBase: true — the BASE class is the user's canonical class
592
+ * (no `*BaseModel` suffix, lives at the grouped path). Nest by
593
+ * group so the FQCN matches the grouped base.
594
+ * 4. flatBase: false + userEditableGroupByFolder: true — the editable
595
+ * mirrors the base group. Nest by group so the FQCN matches the
596
+ * grouped editable.
597
+ * 5. flatBase: false + userEditableGroupByFolder: false (Laravel-
598
+ * canonical default) — the editable is FLAT. Don't nest; the
599
+ * FQCN matches the flat editable's location.
600
+ *
601
+ * Pre-v5.8.6 every relation emitted `\${modelNamespace}\${target}::class`
602
+ * (flat) regardless of where the target's file actually lived. Grouped
603
+ * projects (especially flatBase) hit `ErrorException: include(... no
604
+ * such file)` on every cross-Model reference.
605
+ */
606
+ function resolveRelatedModelNamespace(target, modelNamespace, config, reader) {
607
+ const baseNs = reader.resolveModelNamespace(target, modelNamespace);
608
+ // Package schemas — leave as-is.
609
+ if (baseNs !== modelNamespace)
610
+ return baseNs;
611
+ const targetSchema = reader.getSchema(target);
612
+ if (!targetSchema)
613
+ return baseNs;
614
+ if (targetSchema.kind === 'pivot') {
615
+ return `${baseNs}\\Pivot`;
616
+ }
617
+ // Nest by group when EITHER flatBase is on (base is canonical, grouped)
618
+ // OR the editable explicitly mirrors the base group.
619
+ const shouldNestByGroup = config.models.flatBase || config.models.userEditableGroupByFolder;
620
+ if (shouldNestByGroup && targetSchema.group) {
621
+ return `${baseNs}\\${toPascalCase(targetSchema.group)}`;
622
+ }
623
+ return baseNs;
624
+ }
625
+ function buildRelations(schemaName, properties, propertyOrder, modelNamespace, reader, config) {
582
626
  const methods = [];
583
627
  const sourceTableName = reader.getTableName(schemaName);
584
628
  // Track method names already declared on this model so reverse-relation
@@ -591,9 +635,11 @@ function buildRelations(schemaName, properties, propertyOrder, modelNamespace, r
591
635
  const prop = properties[propName];
592
636
  if (!prop || prop['type'] !== 'Association')
593
637
  continue;
594
- // Resolve namespace per-target (package schemas use their own namespace)
638
+ // Resolve namespace per-target (package schemas use their own namespace).
639
+ // Issue #98 v5.8.6: also nest by target's group / Pivot/ so the
640
+ // FQCN matches the target file's actual location.
595
641
  const target = prop['target'] ?? '';
596
- const ns = target ? reader.resolveModelNamespace(target, modelNamespace) : modelNamespace;
642
+ const ns = target ? resolveRelatedModelNamespace(target, modelNamespace, config, reader) : modelNamespace;
597
643
  const targetTableName = target ? reader.getTableName(target) : '';
598
644
  // For ManyToMany, look up the explicit `kind: pivot` schema (the
599
645
  // sorted PascalCase concat of source + target — auto-created on
@@ -623,7 +669,7 @@ function buildRelations(schemaName, properties, propertyOrder, modelNamespace, r
623
669
  // Auto-generate reverse HasMany/HasOne for any child schema that declares a
624
670
  // ManyToOne/OneToOne pointing at this schema, unless the user has already
625
671
  // declared a relation method with the same name. Issue #39.
626
- const inverseRelations = buildInverseRelations(schemaName, modelNamespace, reader, declaredMethods, explicitInverses);
672
+ const inverseRelations = buildInverseRelations(schemaName, modelNamespace, reader, declaredMethods, explicitInverses, config);
627
673
  if (inverseRelations) {
628
674
  methods.push(inverseRelations);
629
675
  }
@@ -666,7 +712,7 @@ const LARAVEL_RESERVED_RELATION_NAMES = new Set([
666
712
  * a unique method name, or add `mappedBy: <customName>` to the child schema
667
713
  * so the derived method name changes.
668
714
  */
669
- function buildInverseRelations(parentName, modelNamespace, reader, declaredMethods, explicitInverses) {
715
+ function buildInverseRelations(parentName, modelNamespace, reader, declaredMethods, explicitInverses, config) {
670
716
  const methods = [];
671
717
  const allSchemas = reader.getSchemas();
672
718
  // Stable iteration order — sort child names so output is deterministic
@@ -714,7 +760,11 @@ function buildInverseRelations(parentName, modelNamespace, reader, declaredMetho
714
760
  // Here we're emitting the *parent* accessor, so the child's ManyToOne
715
761
  // (or owning OneToOne) is what we react to.
716
762
  const fkColumn = `${toSnakeCase(childPropName)}_id`;
717
- const childNs = reader.resolveModelNamespace(childName, modelNamespace);
763
+ // Issue #98 v5.8.6: nest the inverse-side FQCN by the CHILD's
764
+ // group / Pivot/ so the relation resolves to the child's actual
765
+ // file location (which lives in `<childGroup>/<Child>.php` or
766
+ // `Pivot/<Child>.php`, not flat).
767
+ const childNs = resolveRelatedModelNamespace(childName, modelNamespace, config, reader);
718
768
  const fqcn = `\\${childNs}\\${childName}`;
719
769
  const returnType = isOneToOne ? 'HasOne' : 'HasMany';
720
770
  const eloquentMethod = isOneToOne ? 'hasOne' : 'hasMany';