@omnifyjp/ts 5.8.40 → 5.9.1

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;
@@ -172,7 +185,7 @@ ${imports}
172
185
  ${docProperties} */
173
186
  class ${modelName}BaseModel extends ${baseClass}${implementsClause}
174
187
  {
175
- ${traits} /**
188
+ ${traits}${connectionSection} /**
176
189
  * The table associated with the model.
177
190
  */
178
191
  protected $table = '${tableName}';
@@ -373,12 +386,14 @@ function buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable
373
386
  }
374
387
  }
375
388
  if (hasAuditLog) {
376
- // HasOmnifyAuditLog is a global Omnify trait emitted by
377
- // audit-trait-generator.ts. Same global-traits placement as HasFiles
378
- // so consumers can update the trait in place without per-schema
379
- // duplication.
389
+ // HasOmnifyAuditLog is a GLOBAL Omnify trait emitted by
390
+ // audit-trait-generator.ts at `config.models.baseNamespace\Traits`
391
+ // (e.g. App\Models\Omnify\Base\Traits). The import here must resolve to
392
+ // that SAME global namespace — using the per-model (group) `baseNamespace`
393
+ // (e.g. ...\Base\Logistics) yields `Base\Logistics\Traits\HasOmnifyAuditLog`
394
+ // which does not exist → "Trait not found" fatal on every model load.
380
395
  const auditTraitsNs = config
381
- ? resolveGlobalTraitNamespace(config, baseNamespace + '\\Traits')
396
+ ? resolveGlobalTraitNamespace(config, config.models.baseNamespace + '\\Traits')
382
397
  : (traitsNamespace || baseNamespace + '\\Traits');
383
398
  lines.push(`use ${auditTraitsNs}\\HasOmnifyAuditLog;`);
384
399
  }
@@ -195,7 +195,7 @@ namespace ${baseNamespace};
195
195
  use Illuminate\\Foundation\\Http\\FormRequest;${ruleImport}
196
196
  use ${localesClass};
197
197
 
198
- abstract class ${modelName}${action}RequestBase extends FormRequest
198
+ class ${modelName}${action}RequestBase extends FormRequest
199
199
  {
200
200
  /**
201
201
  * Determine if the user is authorized to make this request.
@@ -277,7 +277,10 @@ ${attributeKeysList}
277
277
  const nested = nestByGroup({ path: raw.path, namespace: '' }, schema.group);
278
278
  baseDescriptor = { className: raw.className, path: nested.path };
279
279
  }
280
- const finalContent = content.replace(new RegExp(`class ${modelName}${action}RequestBase\\b`, 'g'), `class ${baseDescriptor.className}`).replace(new RegExp(`abstract class ${modelName}${action}RequestBase\\b`, 'g'), `abstract class ${baseDescriptor.className}`);
280
+ // Issue #114: the request base is the class controllers inject directly
281
+ // (#101) — so it must be CONCRETE, not abstract (an abstract FormRequest is
282
+ // not container-resolvable → BindingResolutionException / HTTP 500).
283
+ const finalContent = content.replace(new RegExp(`class ${modelName}${action}RequestBase\\b`, 'g'), `class ${baseDescriptor.className}`);
281
284
  return baseFile(`${baseDescriptor.path}/${baseDescriptor.className}.php`, finalContent);
282
285
  }
283
286
  function generateUserRequest(name, schema, config, action) {
@@ -5,9 +5,12 @@ import { SchemaReader } from './schema-reader.js';
5
5
  import type { GeneratedFile, PhpConfig } from './types.js';
6
6
  /** Generate Resource classes for all project-owned visible object schemas.
7
7
  *
8
- * Issue #101 v5.8.23: only the base is emitted. Empty editable stubs
9
- * (every project's 30+ thin `class XResource extends Base {}`) are
10
- * pure scaffolding noise devs hand-create wrappers only when custom
11
- * serialization logic IS needed.
8
+ * Issue #101 v5.8.23 emitted ONLY the base but child resources embed a
9
+ * parent relation via `new \App\Http\Resources\<Parent>Resource(...)` (the
10
+ * editable sibling). With the sibling gone, any loaded relation fatals with a
11
+ * "class not found" Error (issue #114). So the editable sibling is re-emitted
12
+ * as a create-if-missing user file (`overwrite: false`): written once, never
13
+ * regenerated (so it is not the scaffolding noise #101 removed), and the
14
+ * cross-resource reference always resolves.
12
15
  */
13
16
  export declare function generateResources(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -6,10 +6,13 @@ import { isHiddenByDefault, toResourceExpression } from './type-mapper.js';
6
6
  import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup } from './types.js';
7
7
  /** Generate Resource classes for all project-owned visible object schemas.
8
8
  *
9
- * Issue #101 v5.8.23: only the base is emitted. Empty editable stubs
10
- * (every project's 30+ thin `class XResource extends Base {}`) are
11
- * pure scaffolding noise devs hand-create wrappers only when custom
12
- * serialization logic IS needed.
9
+ * Issue #101 v5.8.23 emitted ONLY the base but child resources embed a
10
+ * parent relation via `new \App\Http\Resources\<Parent>Resource(...)` (the
11
+ * editable sibling). With the sibling gone, any loaded relation fatals with a
12
+ * "class not found" Error (issue #114). So the editable sibling is re-emitted
13
+ * as a create-if-missing user file (`overwrite: false`): written once, never
14
+ * regenerated (so it is not the scaffolding noise #101 removed), and the
15
+ * cross-resource reference always resolves.
13
16
  */
14
17
  export function generateResources(reader, config) {
15
18
  const files = [];
@@ -17,6 +20,7 @@ export function generateResources(reader, config) {
17
20
  if (schema.kind === 'extend')
18
21
  continue;
19
22
  files.push(generateBaseResource(name, schema, reader, config));
23
+ files.push(generateUserResource(name, schema, config));
20
24
  }
21
25
  return files;
22
26
  }
@@ -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.40",
3
+ "version": "5.9.1",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",