@omnifyjp/ts 0.4.2 → 1.0.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.
@@ -7,9 +7,9 @@ import { baseFile } from './types.js';
7
7
  export function generateBaseModel(reader, config) {
8
8
  const baseNamespace = config.models.baseNamespace;
9
9
  const modelNamespace = config.models.namespace;
10
- // Build morph map entries from all visible object schemas
10
+ // Build morph map entries from project-owned visible object schemas
11
11
  const morphMap = {};
12
- for (const name of Object.keys(reader.getVisibleObjectSchemas())) {
12
+ for (const name of Object.keys(reader.getProjectVisibleObjectSchemas())) {
13
13
  const modelName = toPascalCase(name);
14
14
  morphMap[modelName] = `\\${modelNamespace}\\${modelName}::class`;
15
15
  }
@@ -3,5 +3,5 @@
3
3
  */
4
4
  import { SchemaReader } from './schema-reader.js';
5
5
  import type { GeneratedFile, PhpConfig } from './types.js';
6
- /** Generate Factory classes for all visible object schemas. */
6
+ /** Generate Factory classes for all project-owned visible object schemas. */
7
7
  export declare function generateFactories(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -4,11 +4,11 @@
4
4
  import { toPascalCase } from './naming-helper.js';
5
5
  import { toFaker, compoundFaker, associationFaker } from './faker-mapper.js';
6
6
  import { userFile } from './types.js';
7
- /** Generate Factory classes for all visible object schemas. */
7
+ /** Generate Factory classes for all project-owned visible object schemas. */
8
8
  export function generateFactories(reader, config) {
9
9
  const files = [];
10
10
  const schemas = reader.getSchemas();
11
- for (const [name, schema] of Object.entries(reader.getVisibleObjectSchemas())) {
11
+ for (const [name, schema] of Object.entries(reader.getProjectVisibleObjectSchemas())) {
12
12
  const file = generateFactory(name, schema, reader, schemas, config);
13
13
  if (file)
14
14
  files.push(file);
@@ -31,7 +31,10 @@ function generateFactory(name, schema, reader, schemas, config) {
31
31
  const type = prop['type'] ?? 'String';
32
32
  // Handle associations (foreign keys)
33
33
  if (type === 'Association') {
34
- const result = associationFaker(propName, prop, modelNamespace, name);
34
+ // Resolve namespace per-target (package schemas use their own model namespace)
35
+ const target = prop['target'] ?? '';
36
+ const targetNs = target ? reader.resolveModelNamespace(target, modelNamespace) : modelNamespace;
37
+ const result = associationFaker(propName, prop, targetNs, name);
35
38
  if (result) {
36
39
  attributes.push(result.fake);
37
40
  if (result.import)
@@ -3,5 +3,5 @@
3
3
  */
4
4
  import { SchemaReader } from './schema-reader.js';
5
5
  import type { GeneratedFile, PhpConfig } from './types.js';
6
- /** Generate Locales classes for all object schemas. */
6
+ /** Generate Locales classes for all project-owned object schemas. */
7
7
  export declare function generateLocales(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -3,10 +3,10 @@
3
3
  */
4
4
  import { toPascalCase, toSnakeCase } from './naming-helper.js';
5
5
  import { baseFile } from './types.js';
6
- /** Generate Locales classes for all object schemas. */
6
+ /** Generate Locales classes for all project-owned object schemas. */
7
7
  export function generateLocales(reader, config) {
8
8
  const files = [];
9
- for (const [name, schema] of Object.entries(reader.getObjectSchemas())) {
9
+ for (const [name, schema] of Object.entries(reader.getProjectObjectSchemas())) {
10
10
  const file = generateLocalesClass(name, schema, reader, config);
11
11
  if (file)
12
12
  files.push(file);
@@ -3,5 +3,6 @@
3
3
  */
4
4
  import { SchemaReader } from './schema-reader.js';
5
5
  import type { GeneratedFile, PhpConfig } from './types.js';
6
- /** Generate base model and user model for all object schemas. */
6
+ /** Generate base model and user model for all project-owned object schemas,
7
+ * plus user models for package schemas (extending the package model). */
7
8
  export declare function generateModels(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -5,12 +5,21 @@ import { toPascalCase, toSnakeCase } from './naming-helper.js';
5
5
  import { toCast, toPhpDocType, isHiddenByDefault } from './type-mapper.js';
6
6
  import { buildRelation } from './relation-builder.js';
7
7
  import { baseFile, userFile } from './types.js';
8
- /** Generate base model and user model for all object schemas. */
8
+ /** Generate base model and user model for all project-owned object schemas,
9
+ * plus user models for package schemas (extending the package model). */
9
10
  export function generateModels(reader, config) {
10
11
  const files = [];
11
- for (const [name, schema] of Object.entries(reader.getObjectSchemas())) {
12
+ // Project-owned schemas: base model + user model
13
+ for (const [name, schema] of Object.entries(reader.getProjectObjectSchemas())) {
12
14
  files.push(...generateForSchema(name, schema, reader, config));
13
15
  }
16
+ // Package schemas: user model only (extends package model)
17
+ for (const [name, schema] of Object.entries(reader.getPackageObjectSchemas())) {
18
+ const pkgNs = reader.resolveModelNamespace(name, config.models.namespace);
19
+ if (pkgNs !== config.models.namespace) {
20
+ files.push(generatePackageUserModel(name, pkgNs, config));
21
+ }
22
+ }
14
23
  return files;
15
24
  }
16
25
  function generateForSchema(name, schema, reader, config) {
@@ -39,7 +48,7 @@ function generateBaseModel(name, schema, reader, config) {
39
48
  const hidden = buildHidden(properties, expandedProperties, propertyOrder);
40
49
  const appends = buildAppends(expandedProperties);
41
50
  const casts = buildCasts(properties, expandedProperties, propertyOrder);
42
- const relations = buildRelations(properties, propertyOrder, modelNamespace);
51
+ const relations = buildRelations(properties, propertyOrder, modelNamespace, reader);
43
52
  const accessors = buildAccessors(expandedProperties);
44
53
  const primaryKey = options['primaryKey'] ?? 'id';
45
54
  const rawId = options.id;
@@ -345,13 +354,16 @@ function buildCasts(properties, expandedProperties, propertyOrder) {
345
354
  return '';
346
355
  return casts.map(c => ` ${c}`).join('\n') + '\n';
347
356
  }
348
- function buildRelations(properties, propertyOrder, modelNamespace) {
357
+ function buildRelations(properties, propertyOrder, modelNamespace, reader) {
349
358
  const methods = [];
350
359
  for (const propName of propertyOrder) {
351
360
  const prop = properties[propName];
352
361
  if (!prop || prop['type'] !== 'Association')
353
362
  continue;
354
- const result = buildRelation(propName, prop, modelNamespace);
363
+ // Resolve namespace per-target (package schemas use their own namespace)
364
+ const target = prop['target'] ?? '';
365
+ const ns = target ? reader.resolveModelNamespace(target, modelNamespace) : modelNamespace;
366
+ const result = buildRelation(propName, prop, ns);
355
367
  if (result) {
356
368
  methods.push('\n' + result.method);
357
369
  }
@@ -381,6 +393,30 @@ function buildAccessors(expandedProperties) {
381
393
  }
382
394
  return methods.join('\n');
383
395
  }
396
+ /** Generate a user model for a package schema that extends the package's model class. */
397
+ function generatePackageUserModel(name, packageNamespace, config) {
398
+ const modelName = toPascalCase(name);
399
+ const modelNamespace = config.models.namespace;
400
+ const packageModelClass = `${packageNamespace}\\${modelName}`;
401
+ const content = `<?php
402
+
403
+ namespace ${modelNamespace};
404
+
405
+ use ${packageModelClass} as Package${modelName};
406
+
407
+ /**
408
+ * ${modelName} Model (extends package model)
409
+ *
410
+ * This file is generated once and can be customized.
411
+ * Add your project-specific methods and relations here.
412
+ */
413
+ class ${modelName} extends Package${modelName}
414
+ {
415
+ // Add your custom methods here
416
+ }
417
+ `;
418
+ return userFile(`${config.models.path}/${modelName}.php`, content);
419
+ }
384
420
  function fullNameAccessor(prefix, suffix, fields, separator) {
385
421
  const methodName = 'get' + toPascalCase(prefix) + suffix + 'Attribute';
386
422
  const fieldAccess = fields.map(f => `$this->${f}`).join(', ');
@@ -3,5 +3,5 @@
3
3
  */
4
4
  import { SchemaReader } from './schema-reader.js';
5
5
  import type { GeneratedFile, PhpConfig } from './types.js';
6
- /** Generate Store/Update request classes for all visible object schemas. */
6
+ /** Generate Store/Update request classes for all project-owned visible object schemas. */
7
7
  export declare function generateRequests(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -4,10 +4,10 @@
4
4
  import { toPascalCase, toSnakeCase, toCamelCase } from './naming-helper.js';
5
5
  import { toStoreRules, toUpdateRules, formatRules, hasRuleObject } from './type-mapper.js';
6
6
  import { baseFile, userFile } from './types.js';
7
- /** Generate Store/Update request classes for all visible object schemas. */
7
+ /** Generate Store/Update request classes for all project-owned visible object schemas. */
8
8
  export function generateRequests(reader, config) {
9
9
  const files = [];
10
- for (const [name, schema] of Object.entries(reader.getVisibleObjectSchemas())) {
10
+ for (const [name, schema] of Object.entries(reader.getProjectVisibleObjectSchemas())) {
11
11
  files.push(...generateForSchema(name, schema, reader, config));
12
12
  }
13
13
  return files;
@@ -3,5 +3,5 @@
3
3
  */
4
4
  import { SchemaReader } from './schema-reader.js';
5
5
  import type { GeneratedFile, PhpConfig } from './types.js';
6
- /** Generate Resource classes for all visible object schemas. */
6
+ /** Generate Resource classes for all project-owned visible object schemas. */
7
7
  export declare function generateResources(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -4,10 +4,10 @@
4
4
  import { toPascalCase, toSnakeCase, toCamelCase } from './naming-helper.js';
5
5
  import { isHiddenByDefault, toResourceExpression } from './type-mapper.js';
6
6
  import { baseFile, userFile } from './types.js';
7
- /** Generate Resource classes for all visible object schemas. */
7
+ /** Generate Resource classes for all project-owned visible object schemas. */
8
8
  export function generateResources(reader, config) {
9
9
  const files = [];
10
- for (const [name, schema] of Object.entries(reader.getVisibleObjectSchemas())) {
10
+ for (const [name, schema] of Object.entries(reader.getProjectVisibleObjectSchemas())) {
11
11
  files.push(generateBaseResource(name, schema, reader, config));
12
12
  files.push(generateUserResource(name, config));
13
13
  }
@@ -38,7 +38,7 @@ function generateBaseResource(name, schema, reader, config) {
38
38
  if (isHiddenByDefault(type) || hidden)
39
39
  continue;
40
40
  if (type === 'Association') {
41
- addAssociationFields(propName, prop, fields, resourceNamespace, modelNamespace);
41
+ addAssociationFields(propName, prop, fields, resourceNamespace, modelNamespace, reader);
42
42
  continue;
43
43
  }
44
44
  if (expandedProperties[propName]) {
@@ -150,26 +150,37 @@ class ${modelName}Resource extends ${modelName}ResourceBase
150
150
  `;
151
151
  return userFile(`${config.resources.path}/${modelName}Resource.php`, content);
152
152
  }
153
- function addAssociationFields(propName, prop, fields, resourceNamespace, modelNamespace) {
153
+ function addAssociationFields(propName, prop, fields, resourceNamespace, modelNamespace, reader) {
154
154
  const relation = prop['relation'] ?? '';
155
155
  const target = prop['target'] ?? '';
156
156
  const methodName = toCamelCase(propName);
157
+ // Resolve resource namespace for the target (null if package without resource ns)
158
+ const targetResNs = target ? reader.resolveResourceNamespace(target, resourceNamespace) : resourceNamespace;
159
+ // If target is a package schema without a resource namespace, use simple whenLoaded
160
+ if (!targetResNs && relation !== 'MorphTo') {
161
+ if (relation === 'ManyToOne') {
162
+ const snakeName = toSnakeCase(propName) + '_id';
163
+ fields.push(` '${snakeName}' => $this->${snakeName},`);
164
+ }
165
+ fields.push(` '${methodName}' => $this->whenLoaded('${methodName}'),`);
166
+ return;
167
+ }
157
168
  const targetResource = toPascalCase(target) + 'Resource';
158
169
  switch (relation) {
159
170
  case 'ManyToOne': {
160
171
  const snakeName = toSnakeCase(propName) + '_id';
161
172
  fields.push(` '${snakeName}' => $this->${snakeName},`);
162
- fields.push(` '${methodName}' => $this->whenLoaded('${methodName}', fn() => new \\${resourceNamespace}\\${targetResource}($this->${methodName})),`);
173
+ fields.push(` '${methodName}' => $this->whenLoaded('${methodName}', fn() => new \\${targetResNs}\\${targetResource}($this->${methodName})),`);
163
174
  break;
164
175
  }
165
176
  case 'OneToMany':
166
177
  case 'ManyToMany':
167
178
  case 'MorphMany':
168
- fields.push(` '${methodName}' => $this->whenLoaded('${methodName}', fn() => \\${resourceNamespace}\\${targetResource}::collection($this->${methodName})),`);
179
+ fields.push(` '${methodName}' => $this->whenLoaded('${methodName}', fn() => \\${targetResNs}\\${targetResource}::collection($this->${methodName})),`);
169
180
  break;
170
181
  case 'OneToOne':
171
182
  case 'MorphOne':
172
- fields.push(` '${methodName}' => $this->whenLoaded('${methodName}', fn() => new \\${resourceNamespace}\\${targetResource}($this->${methodName})),`);
183
+ fields.push(` '${methodName}' => $this->whenLoaded('${methodName}', fn() => new \\${targetResNs}\\${targetResource}($this->${methodName})),`);
173
184
  break;
174
185
  case 'MorphTo':
175
186
  fields.push(` '${methodName}' => $this->whenLoaded('${methodName}'),`);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Port of SchemaReader.php — reads schemas.json data.
3
3
  */
4
- import type { SchemasJson, SchemaDefinition, ExpandedProperty } from '../types.js';
4
+ import type { SchemasJson, SchemaDefinition, ExpandedProperty, PackageExportInfo } from '../types.js';
5
5
  export declare class SchemaReader {
6
6
  private data;
7
7
  constructor(data: SchemasJson);
@@ -11,6 +11,28 @@ export declare class SchemaReader {
11
11
  getObjectSchemas(): Record<string, SchemaDefinition>;
12
12
  getVisibleObjectSchemas(): Record<string, SchemaDefinition>;
13
13
  getEnumSchemas(): Record<string, SchemaDefinition>;
14
+ /** Check if a schema belongs to a package. */
15
+ isPackageSchema(name: string): boolean;
16
+ /** Object schemas owned by the project (not from packages). */
17
+ getProjectObjectSchemas(): Record<string, SchemaDefinition>;
18
+ /** Object schemas from packages (not project-owned). */
19
+ getPackageObjectSchemas(): Record<string, SchemaDefinition>;
20
+ /** Visible object schemas owned by the project. */
21
+ getProjectVisibleObjectSchemas(): Record<string, SchemaDefinition>;
22
+ /** Get the top-level packages map. */
23
+ getPackages(): Record<string, PackageExportInfo>;
24
+ /**
25
+ * Resolve the model namespace for a target schema.
26
+ * - Project schema → uses the project's model namespace
27
+ * - Package schema → uses the package's codegen.laravel.model.namespace (if set)
28
+ */
29
+ resolveModelNamespace(targetName: string, projectModelNamespace: string): string;
30
+ /**
31
+ * Resolve the resource namespace for a target schema.
32
+ * Returns null if the target is a package schema without a resource namespace
33
+ * (meaning no resource class exists for it in the project).
34
+ */
35
+ resolveResourceNamespace(targetName: string, projectResourceNamespace: string): string | null;
14
36
  getCompoundTypes(): Record<string, unknown>;
15
37
  getSimpleTypes(): Record<string, unknown>;
16
38
  getCustomEnums(): Record<string, string[]>;
@@ -9,6 +9,9 @@ export class SchemaReader {
9
9
  static fromData(data) {
10
10
  return new SchemaReader(data);
11
11
  }
12
+ // ---------------------------------------------------------------------------
13
+ // All schemas (project + package)
14
+ // ---------------------------------------------------------------------------
12
15
  getSchemas() {
13
16
  return this.data.schemas ?? {};
14
17
  }
@@ -43,6 +46,85 @@ export class SchemaReader {
43
46
  }
44
47
  return result;
45
48
  }
49
+ // ---------------------------------------------------------------------------
50
+ // Project-only schemas (package === null or undefined)
51
+ // ---------------------------------------------------------------------------
52
+ /** Check if a schema belongs to a package. */
53
+ isPackageSchema(name) {
54
+ const schema = this.getSchema(name);
55
+ return schema?.package != null;
56
+ }
57
+ /** Object schemas owned by the project (not from packages). */
58
+ getProjectObjectSchemas() {
59
+ const result = {};
60
+ for (const [name, schema] of Object.entries(this.getObjectSchemas())) {
61
+ if (schema.package == null) {
62
+ result[name] = schema;
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+ /** Object schemas from packages (not project-owned). */
68
+ getPackageObjectSchemas() {
69
+ const result = {};
70
+ for (const [name, schema] of Object.entries(this.getObjectSchemas())) {
71
+ if (schema.package != null) {
72
+ result[name] = schema;
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ /** Visible object schemas owned by the project. */
78
+ getProjectVisibleObjectSchemas() {
79
+ const result = {};
80
+ for (const [name, schema] of Object.entries(this.getProjectObjectSchemas())) {
81
+ if (!schema.options?.hidden) {
82
+ result[name] = schema;
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Packages
89
+ // ---------------------------------------------------------------------------
90
+ /** Get the top-level packages map. */
91
+ getPackages() {
92
+ return this.data.packages ?? {};
93
+ }
94
+ /**
95
+ * Resolve the model namespace for a target schema.
96
+ * - Project schema → uses the project's model namespace
97
+ * - Package schema → uses the package's codegen.laravel.model.namespace (if set)
98
+ */
99
+ resolveModelNamespace(targetName, projectModelNamespace) {
100
+ const target = this.getSchema(targetName);
101
+ if (target?.package) {
102
+ const pkg = this.getPackages()[target.package];
103
+ const pkgNs = pkg?.codegen?.laravel?.model?.namespace;
104
+ if (pkgNs)
105
+ return pkgNs;
106
+ }
107
+ return projectModelNamespace;
108
+ }
109
+ /**
110
+ * Resolve the resource namespace for a target schema.
111
+ * Returns null if the target is a package schema without a resource namespace
112
+ * (meaning no resource class exists for it in the project).
113
+ */
114
+ resolveResourceNamespace(targetName, projectResourceNamespace) {
115
+ const target = this.getSchema(targetName);
116
+ if (target?.package) {
117
+ const pkg = this.getPackages()[target.package];
118
+ const pkgNs = pkg?.codegen?.laravel?.resource?.namespace;
119
+ if (pkgNs)
120
+ return pkgNs;
121
+ return null; // no resource namespace → no resource class
122
+ }
123
+ return projectResourceNamespace;
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // Property helpers
127
+ // ---------------------------------------------------------------------------
46
128
  getCompoundTypes() {
47
129
  return this.data.customTypes?.compound ?? {};
48
130
  }
@@ -6,9 +6,9 @@ import { baseFile } from './types.js';
6
6
  /** Generate the OmnifyServiceProvider for the application. */
7
7
  export function generateServiceProvider(reader, config) {
8
8
  const modelNamespace = config.models.namespace;
9
- // Build morph map entries
9
+ // Build morph map entries (project-owned schemas only)
10
10
  const morphEntries = [];
11
- for (const name of Object.keys(reader.getVisibleObjectSchemas())) {
11
+ for (const name of Object.keys(reader.getProjectVisibleObjectSchemas())) {
12
12
  const modelName = toPascalCase(name);
13
13
  morphEntries.push(` '${modelName}' => \\${modelNamespace}\\${modelName}::class,`);
14
14
  }
package/dist/types.d.ts CHANGED
@@ -22,8 +22,33 @@ export interface SchemasJson {
22
22
  readonly simple: Record<string, SimpleTypeDefinition>;
23
23
  readonly enums: Record<string, string[]>;
24
24
  };
25
+ readonly packages?: Record<string, PackageExportInfo>;
25
26
  readonly schemas: Record<string, SchemaDefinition>;
26
27
  }
28
+ /** Package metadata in schemas.json (codegen namespace references). */
29
+ export interface PackageExportInfo {
30
+ readonly codegen?: PackageCodegenExport;
31
+ }
32
+ /** Codegen namespace info from a package. */
33
+ export interface PackageCodegenExport {
34
+ readonly laravel?: {
35
+ readonly model?: {
36
+ readonly namespace?: string;
37
+ };
38
+ readonly request?: {
39
+ readonly namespace?: string;
40
+ };
41
+ readonly resource?: {
42
+ readonly namespace?: string;
43
+ };
44
+ readonly factory?: {
45
+ readonly namespace?: string;
46
+ };
47
+ };
48
+ readonly typescript?: {
49
+ readonly modelsPath?: string;
50
+ };
51
+ }
27
52
  /** Compound type definition (e.g., JapaneseName). */
28
53
  export interface CompoundTypeDefinition {
29
54
  readonly fields: readonly CompoundField[];
@@ -68,6 +93,7 @@ export interface SchemaOptions {
68
93
  /** Schema definition from schemas.json. */
69
94
  export interface SchemaDefinition {
70
95
  readonly name: string;
96
+ readonly package?: string | null;
71
97
  readonly tableName?: string;
72
98
  readonly connection?: string;
73
99
  readonly displayName?: LocalizedString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "0.4.2",
3
+ "version": "1.0.0",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",