@omnifyjp/ts 5.8.39 → 5.9.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.
package/dist/generator.js CHANGED
@@ -333,15 +333,43 @@ function generateI18nFile(options) {
333
333
  /** Generate index.ts re-exports. */
334
334
  function generateIndexFile(schemas, schemaEnums, pluginEnums, typeAliases, hasFiles = false) {
335
335
  const parts = [generateBaseHeader()];
336
- // Common types
336
+ // Common types.
337
+ // A generator-owned common type can collide with a user model schema of the
338
+ // same name (e.g. a `Locale` entity vs the `Locale` locale-code union). The
339
+ // model is the user's explicit schema and stays canonical (keeps the bare
340
+ // name in the model section below); the common type is re-exported under an
341
+ // alias here so the barrel never emits a duplicate identifier (TS2300).
337
342
  parts.push(`// Common Types\n`);
343
+ const modelNames = new Set(Object.values(schemas)
344
+ .filter((s) => s.kind !== 'enum' && s.options?.hidden !== true)
345
+ .map((s) => s.name));
346
+ // Preferred, human-readable aliases for the known common types. Any other
347
+ // collision falls back to a `Common<Name>` alias.
348
+ const COMMON_TYPE_ALIASES = {
349
+ Locale: 'LocaleCode',
350
+ LocaleMap: 'LocaleStringMap',
351
+ DateTimeString: 'CommonDateTimeString',
352
+ DateString: 'CommonDateString',
353
+ OmnifyFile: 'OmnifyFileType',
354
+ };
355
+ const commonTypeNames = hasFiles
356
+ ? ['LocaleMap', 'Locale', 'DateTimeString', 'DateString', 'OmnifyFile']
357
+ : ['LocaleMap', 'Locale', 'DateTimeString', 'DateString'];
358
+ const commonTypeExports = commonTypeNames.map((name) => {
359
+ if (!modelNames.has(name)) {
360
+ return name;
361
+ }
362
+ const alias = COMMON_TYPE_ALIASES[name] ?? `Common${name}`;
363
+ // eslint-disable-next-line no-console
364
+ console.warn(`[omnify-ts] Common type "${name}" collides with model schema "${name}"; ` +
365
+ `re-exporting the common type from index.ts as "${alias}". ` +
366
+ `The "${name}" model keeps the canonical name.`);
367
+ return `${name} as ${alias}`;
368
+ });
369
+ parts.push(`export type { ${commonTypeExports.join(', ')} } from './common';\n`);
338
370
  if (hasFiles) {
339
- parts.push(`export type { LocaleMap, Locale, DateTimeString, DateString, OmnifyFile } from './common';\n`);
340
371
  parts.push(`export { OmnifyFileSchema } from './common';\n`);
341
372
  }
342
- else {
343
- parts.push(`export type { LocaleMap, Locale, DateTimeString, DateString } from './common';\n`);
344
- }
345
373
  // I18n
346
374
  parts.push(`// i18n (Internationalization)\n`);
347
375
  parts.push(`export {\n`);
@@ -77,6 +77,19 @@ function generateBaseModel(name, schema, reader, config) {
77
77
  const expandedProperties = reader.getExpandedProperties(name);
78
78
  const propertyOrder = reader.getPropertyOrder(name);
79
79
  const tableName = reader.getTableName(name);
80
+ // Multi-database routing: a schema may declare a top-level `connection:`
81
+ // (matching an omnify.yaml connections key + a Laravel connection). Emit the
82
+ // matching `$connection` so the model resolves to the right database. The
83
+ // implicit `default` connection is left unset so Laravel uses its default.
84
+ const connectionName = schema['connection'];
85
+ const connectionSection = connectionName && connectionName !== 'default'
86
+ ? `
87
+ /**
88
+ * The database connection for the model.
89
+ */
90
+ protected $connection = '${connectionName}';
91
+ `
92
+ : '';
80
93
  const isAuthenticatable = options['authenticatable'] ?? false;
81
94
  const hasSoftDelete = options.softDelete ?? false;
82
95
  const hasTimestamps = options.timestamps ?? false;
@@ -116,6 +129,31 @@ function generateBaseModel(name, schema, reader, config) {
116
129
  const fileAccessors = hasFiles ? buildFileAccessors(properties, propertyOrder, modelNamespace) : '';
117
130
  const auditSection = buildAuditSection(options, reader, modelNamespace);
118
131
  const nestedSetMethod = buildNestedSetParentIdMethod(nestedSetParentColumn);
132
+ // Authenticatable base models extend Illuminate's Authenticatable, not
133
+ // BaseModel, so they cannot inherit its getMorphClass() override. Give them
134
+ // their own copy so application subclasses (e.g.
135
+ // `App\Models\User extends App\Models\Omnify\User`) still resolve under an
136
+ // enforced morph map instead of throwing ClassMorphViolationException. #106
137
+ const morphClassMethod = isAuthenticatable ? `
138
+ /**
139
+ * Resolve the polymorphic alias from the morph map, walking up the class
140
+ * hierarchy so application subclasses of this generated model still resolve.
141
+ */
142
+ public function getMorphClass(): string
143
+ {
144
+ $morphMap = \\Illuminate\\Database\\Eloquent\\Relations\\Relation::morphMap();
145
+
146
+ for ($class = static::class; $class !== false; $class = get_parent_class($class)) {
147
+ $alias = array_search($class, $morphMap, true);
148
+
149
+ if ($alias !== false) {
150
+ return $alias;
151
+ }
152
+ }
153
+
154
+ return parent::getMorphClass();
155
+ }
156
+ ` : '';
119
157
  let keyTypeSection = '';
120
158
  if (idType === 'Uuid' || idType === 'Ulid' || idType === 'String') {
121
159
  keyTypeSection = `
@@ -147,7 +185,7 @@ ${imports}
147
185
  ${docProperties} */
148
186
  class ${modelName}BaseModel extends ${baseClass}${implementsClause}
149
187
  {
150
- ${traits} /**
188
+ ${traits}${connectionSection} /**
151
189
  * The table associated with the model.
152
190
  */
153
191
  protected $table = '${tableName}';
@@ -212,7 +250,7 @@ ${appends} ];
212
250
  return [
213
251
  ${casts} ];
214
252
  }
215
- ${auditLogProperties}${relations}${accessors}${fileAccessors}${auditSection}${nestedSetMethod}
253
+ ${auditLogProperties}${relations}${accessors}${fileAccessors}${auditSection}${nestedSetMethod}${morphClassMethod}
216
254
  }
217
255
  `;
218
256
  // Replace placeholder class name so the file matches resolved className
@@ -89,8 +89,15 @@ class OmnifyServiceProvider extends ServiceProvider
89
89
  */
90
90
  public function boot(): void
91
91
  {
92
- // Load Omnify migrations from custom directory
93
- $this->loadMigrationsFrom(database_path('migrations/omnify'));
92
+ // Load Omnify migrations: the default-connection directory plus every
93
+ // per-connection subdirectory (multi-database setups route each domain's
94
+ // migrations into its own subfolder; those migrations target their
95
+ // connection via Schema::connection(), so a single migrate run suffices).
96
+ $omnifyMigrations = database_path('migrations/omnify');
97
+ $this->loadMigrationsFrom($omnifyMigrations);
98
+ foreach (glob($omnifyMigrations.'/*', GLOB_ONLYDIR) as $connectionDir) {
99
+ $this->loadMigrationsFrom($connectionDir);
100
+ }
94
101
  ${packageMigrationsBlock}
95
102
  // Register morph map for polymorphic relationships
96
103
  Relation::enforceMorphMap([
@@ -17,7 +17,9 @@ export function generateTranslationModels(reader, config) {
17
17
  const translatableFields = reader.getTranslatableFieldDetails(name);
18
18
  if (translatableFields.length === 0)
19
19
  continue;
20
- files.push(generateTranslationBaseModel(name, translatableFields, enforceLanguageFk, config));
20
+ // A translation table co-locates with its parent — same connection/database.
21
+ const connectionName = reader.getSchema(name)?.['connection'];
22
+ files.push(generateTranslationBaseModel(name, translatableFields, enforceLanguageFk, config, connectionName));
21
23
  files.push(generateTranslationUserModel(name, config));
22
24
  }
23
25
  return files;
@@ -53,9 +55,19 @@ function castFor(type) {
53
55
  return undefined;
54
56
  }
55
57
  }
56
- function generateTranslationBaseModel(name, translatableFields, enforceLanguageFk, config) {
58
+ function generateTranslationBaseModel(name, translatableFields, enforceLanguageFk, config, connectionName) {
57
59
  const modelName = toPascalCase(name);
58
60
  const modelSnake = toSnakeCase(name);
61
+ // Multi-database routing: follow the parent schema's connection so the
62
+ // `*_translations` table resolves to the same database as its parent.
63
+ const connectionSection = connectionName && connectionName !== 'default'
64
+ ? `
65
+ /**
66
+ * The database connection for the model.
67
+ */
68
+ protected $connection = '${connectionName}';
69
+ `
70
+ : '';
59
71
  // Issue #98 v5.8.2: consolidate every Translation model under one
60
72
  // dedicated `Translation/` subfolder + sub-namespace. Reporter's
61
73
  // rationale: 39 translation models flat at the top of `Models/`
@@ -123,7 +135,7 @@ use Illuminate\\Database\\Eloquent\\Model;
123
135
  */
124
136
  class ${baseClassName} extends Model
125
137
  {
126
- /**
138
+ ${connectionSection} /**
127
139
  * The table associated with the model.
128
140
  */
129
141
  protected $table = '${tableName}';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "5.8.39",
3
+ "version": "5.9.0",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",