@omnifyjp/ts 0.3.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/cli.d.ts +13 -0
- package/dist/cli.js +180 -0
- package/dist/enum-generator.d.ts +28 -0
- package/dist/enum-generator.js +253 -0
- package/dist/generator.d.ts +19 -0
- package/dist/generator.js +330 -0
- package/dist/i18n-generator.d.ts +10 -0
- package/dist/i18n-generator.js +143 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/interface-generator.d.ts +31 -0
- package/dist/interface-generator.js +341 -0
- package/dist/php/base-model-generator.d.ts +7 -0
- package/dist/php/base-model-generator.js +70 -0
- package/dist/php/factory-generator.d.ts +7 -0
- package/dist/php/factory-generator.js +95 -0
- package/dist/php/faker-mapper.d.ts +12 -0
- package/dist/php/faker-mapper.js +206 -0
- package/dist/php/index.d.ts +18 -0
- package/dist/php/index.js +40 -0
- package/dist/php/locales-generator.d.ts +7 -0
- package/dist/php/locales-generator.js +135 -0
- package/dist/php/model-generator.d.ts +7 -0
- package/dist/php/model-generator.js +396 -0
- package/dist/php/naming-helper.d.ts +22 -0
- package/dist/php/naming-helper.js +61 -0
- package/dist/php/relation-builder.d.ts +12 -0
- package/dist/php/relation-builder.js +147 -0
- package/dist/php/request-generator.d.ts +7 -0
- package/dist/php/request-generator.js +221 -0
- package/dist/php/resource-generator.d.ts +7 -0
- package/dist/php/resource-generator.js +178 -0
- package/dist/php/schema-reader.d.ts +28 -0
- package/dist/php/schema-reader.js +79 -0
- package/dist/php/service-provider-generator.d.ts +7 -0
- package/dist/php/service-provider-generator.js +64 -0
- package/dist/php/trait-generator.d.ts +6 -0
- package/dist/php/trait-generator.js +104 -0
- package/dist/php/type-mapper.d.ts +25 -0
- package/dist/php/type-mapper.js +217 -0
- package/dist/php/types.d.ts +61 -0
- package/dist/php/types.js +68 -0
- package/dist/types.d.ts +196 -0
- package/dist/types.js +6 -0
- package/dist/zod-generator.d.ts +32 -0
- package/dist/zod-generator.js +428 -0
- package/package.json +31 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omnify/ts — TypeScript Interface Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript interfaces from schema definitions.
|
|
5
|
+
* Handles compound type expansion via expandedProperties, association types,
|
|
6
|
+
* enum references, and all standard property types.
|
|
7
|
+
*/
|
|
8
|
+
/** Convert PascalCase/camelCase to snake_case. */
|
|
9
|
+
export function toSnakeCase(str) {
|
|
10
|
+
return str
|
|
11
|
+
.replace(/([A-Z])/g, '_$1')
|
|
12
|
+
.replace(/^_/, '')
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
/** Maps Omnify property types to TypeScript types. */
|
|
16
|
+
const TYPE_MAP = {
|
|
17
|
+
String: 'string',
|
|
18
|
+
TinyInt: 'number',
|
|
19
|
+
Int: 'number',
|
|
20
|
+
BigInt: 'number',
|
|
21
|
+
Float: 'number',
|
|
22
|
+
Decimal: 'number',
|
|
23
|
+
Boolean: 'boolean',
|
|
24
|
+
Text: 'string',
|
|
25
|
+
MediumText: 'string',
|
|
26
|
+
LongText: 'string',
|
|
27
|
+
Date: 'DateString',
|
|
28
|
+
Time: 'string',
|
|
29
|
+
DateTime: 'DateTimeString',
|
|
30
|
+
Timestamp: 'DateTimeString',
|
|
31
|
+
Json: 'unknown',
|
|
32
|
+
Email: 'string',
|
|
33
|
+
Password: 'string',
|
|
34
|
+
Enum: 'string',
|
|
35
|
+
Select: 'string',
|
|
36
|
+
Lookup: 'number',
|
|
37
|
+
};
|
|
38
|
+
/** Maps primary key types to TypeScript types. */
|
|
39
|
+
const PK_TYPE_MAP = {
|
|
40
|
+
Int: 'number',
|
|
41
|
+
BigInt: 'number',
|
|
42
|
+
Uuid: 'string',
|
|
43
|
+
String: 'string',
|
|
44
|
+
};
|
|
45
|
+
/** Gets TypeScript type for a property. */
|
|
46
|
+
export function getPropertyType(property, allSchemas) {
|
|
47
|
+
if (property.type === 'File') {
|
|
48
|
+
return 'File | null';
|
|
49
|
+
}
|
|
50
|
+
if (property.type === 'Association') {
|
|
51
|
+
const targetName = property.target ?? 'unknown';
|
|
52
|
+
switch (property.relation) {
|
|
53
|
+
case 'OneToOne':
|
|
54
|
+
case 'ManyToOne':
|
|
55
|
+
return targetName;
|
|
56
|
+
case 'OneToMany':
|
|
57
|
+
case 'ManyToMany':
|
|
58
|
+
return `${targetName}[]`;
|
|
59
|
+
case 'MorphTo':
|
|
60
|
+
if (property.targets && property.targets.length > 0) {
|
|
61
|
+
return property.targets.join(' | ');
|
|
62
|
+
}
|
|
63
|
+
return 'unknown';
|
|
64
|
+
case 'MorphOne':
|
|
65
|
+
return targetName;
|
|
66
|
+
case 'MorphMany':
|
|
67
|
+
case 'MorphToMany':
|
|
68
|
+
case 'MorphedByMany':
|
|
69
|
+
return `${targetName}[]`;
|
|
70
|
+
default:
|
|
71
|
+
return 'unknown';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (property.type === 'EnumRef') {
|
|
75
|
+
if (typeof property.enum === 'string') {
|
|
76
|
+
return property.enum;
|
|
77
|
+
}
|
|
78
|
+
return 'string';
|
|
79
|
+
}
|
|
80
|
+
if (property.type === 'Enum') {
|
|
81
|
+
if (typeof property.enum === 'string') {
|
|
82
|
+
return property.enum;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(property.enum)) {
|
|
85
|
+
return property.enum.map(v => `'${v}'`).join(' | ');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return TYPE_MAP[property.type] ?? 'string';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Convert a property to TSProperty array.
|
|
92
|
+
* Compound types expand to multiple properties via expandedProperties.
|
|
93
|
+
* MorphTo and ManyToOne also return multiple properties (FK + relation).
|
|
94
|
+
*/
|
|
95
|
+
export function propertyToTSProperties(propertyName, property, schema, allSchemas, options) {
|
|
96
|
+
const isReadonly = false;
|
|
97
|
+
const isNullable = property.nullable ?? false;
|
|
98
|
+
// Handle compound types via expandedProperties
|
|
99
|
+
if (schema.expandedProperties?.[propertyName]) {
|
|
100
|
+
const expanded = schema.expandedProperties[propertyName];
|
|
101
|
+
const props = [];
|
|
102
|
+
// Find compound type definition for enum resolution
|
|
103
|
+
const compoundDef = options.customTypes.compound[expanded.sourceType];
|
|
104
|
+
for (const col of expanded.columns) {
|
|
105
|
+
// Determine TypeScript type for expanded column
|
|
106
|
+
let tsType;
|
|
107
|
+
if (col.type === 'Enum' && col.enum) {
|
|
108
|
+
// Check if this field has an enumRef in the compound type definition
|
|
109
|
+
const compoundField = compoundDef?.fields.find(f => f.suffix === col.suffix);
|
|
110
|
+
if (compoundField?.enumRef) {
|
|
111
|
+
tsType = compoundField.enumRef;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
tsType = col.enum.map(v => `'${v}'`).join(' | ');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
tsType = TYPE_MAP[col.type] ?? 'string';
|
|
119
|
+
}
|
|
120
|
+
props.push({
|
|
121
|
+
name: col.name,
|
|
122
|
+
type: tsType,
|
|
123
|
+
optional: col.nullable ?? false,
|
|
124
|
+
readonly: isReadonly,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return props;
|
|
128
|
+
}
|
|
129
|
+
// Handle simple custom types (JapanesePhone, JapanesePostalCode, etc.)
|
|
130
|
+
const simpleType = options.customTypes.simple[property.type];
|
|
131
|
+
if (simpleType) {
|
|
132
|
+
const tsType = TYPE_MAP[simpleType.mapsTo] ?? 'string';
|
|
133
|
+
return [{
|
|
134
|
+
name: propertyName,
|
|
135
|
+
type: tsType,
|
|
136
|
+
optional: isNullable,
|
|
137
|
+
readonly: isReadonly,
|
|
138
|
+
}];
|
|
139
|
+
}
|
|
140
|
+
// Handle Association: MorphTo — creates _type, _id, and relation properties
|
|
141
|
+
if (property.type === 'Association' && property.relation === 'MorphTo' && property.targets && property.targets.length > 0) {
|
|
142
|
+
const targetUnion = property.targets.map(t => `'${t}'`).join(' | ');
|
|
143
|
+
const relationUnion = property.targets.join(' | ');
|
|
144
|
+
return [
|
|
145
|
+
{
|
|
146
|
+
name: `${propertyName}_type`,
|
|
147
|
+
type: targetUnion,
|
|
148
|
+
optional: true,
|
|
149
|
+
readonly: isReadonly,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: `${propertyName}_id`,
|
|
153
|
+
type: 'number',
|
|
154
|
+
optional: true,
|
|
155
|
+
readonly: isReadonly,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: propertyName,
|
|
159
|
+
type: `${relationUnion} | null`,
|
|
160
|
+
optional: true,
|
|
161
|
+
readonly: isReadonly,
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
// Handle Association: ManyToOne / OneToOne (owning side) — creates FK _id and relation
|
|
166
|
+
if (property.type === 'Association' && (property.relation === 'ManyToOne' || property.relation === 'OneToOne') && !property.mappedBy) {
|
|
167
|
+
const targetName = property.target ?? 'unknown';
|
|
168
|
+
// Determine FK type from target schema
|
|
169
|
+
let fkType = 'number';
|
|
170
|
+
const targetSchema = property.target ? allSchemas[property.target] : undefined;
|
|
171
|
+
const effectiveIdType = targetSchema?.options?.idType ?? 'BigInt';
|
|
172
|
+
if (effectiveIdType === 'Uuid' || effectiveIdType === 'String') {
|
|
173
|
+
fkType = 'string';
|
|
174
|
+
}
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
name: `${propertyName}_id`,
|
|
178
|
+
type: fkType,
|
|
179
|
+
optional: isNullable,
|
|
180
|
+
readonly: isReadonly,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: propertyName,
|
|
184
|
+
type: isNullable ? `${targetName} | null` : targetName,
|
|
185
|
+
optional: true,
|
|
186
|
+
readonly: isReadonly,
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
// Handle other Association types (OneToMany, ManyToMany, MorphMany, etc.)
|
|
191
|
+
if (property.type === 'Association') {
|
|
192
|
+
const type = getPropertyType(property, allSchemas);
|
|
193
|
+
return [{
|
|
194
|
+
name: propertyName,
|
|
195
|
+
type,
|
|
196
|
+
optional: true,
|
|
197
|
+
readonly: isReadonly,
|
|
198
|
+
}];
|
|
199
|
+
}
|
|
200
|
+
// Default: single property
|
|
201
|
+
const type = getPropertyType(property, allSchemas);
|
|
202
|
+
return [{
|
|
203
|
+
name: propertyName,
|
|
204
|
+
type,
|
|
205
|
+
optional: isNullable,
|
|
206
|
+
readonly: isReadonly,
|
|
207
|
+
}];
|
|
208
|
+
}
|
|
209
|
+
/** Extract referenced schema names from a type string. */
|
|
210
|
+
function extractTypeReferences(type, allSchemaNames) {
|
|
211
|
+
const primitives = new Set(['string', 'number', 'boolean', 'unknown', 'null', 'undefined', 'void', 'never', 'any']);
|
|
212
|
+
const refs = [];
|
|
213
|
+
const cleanType = type.replace(/\[\]/g, '').replace(/\s*\|\s*null/g, '');
|
|
214
|
+
const parts = cleanType.split(/\s*\|\s*/);
|
|
215
|
+
for (const part of parts) {
|
|
216
|
+
const trimmed = part.trim().replace(/^['"]|['"]$/g, '');
|
|
217
|
+
if (!primitives.has(trimmed) && allSchemaNames.has(trimmed)) {
|
|
218
|
+
refs.push(trimmed);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return refs;
|
|
222
|
+
}
|
|
223
|
+
/** Generate a TSInterface from a schema definition. */
|
|
224
|
+
export function schemaToInterface(schema, allSchemas, options) {
|
|
225
|
+
const properties = [];
|
|
226
|
+
const allSchemaNames = new Set(Object.keys(allSchemas).filter(name => allSchemas[name]?.kind !== 'enum'));
|
|
227
|
+
// ID property
|
|
228
|
+
if (schema.options?.id !== false) {
|
|
229
|
+
const pkType = (schema.options?.idType ?? 'BigInt');
|
|
230
|
+
properties.push({
|
|
231
|
+
name: 'id',
|
|
232
|
+
type: PK_TYPE_MAP[pkType] ?? 'number',
|
|
233
|
+
optional: false,
|
|
234
|
+
readonly: false,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// Schema properties (in propertyOrder if available)
|
|
238
|
+
if (schema.properties) {
|
|
239
|
+
const propNames = schema.propertyOrder ?? Object.keys(schema.properties);
|
|
240
|
+
for (const propName of propNames) {
|
|
241
|
+
const property = schema.properties[propName];
|
|
242
|
+
if (!property)
|
|
243
|
+
continue;
|
|
244
|
+
properties.push(...propertyToTSProperties(propName, property, schema, allSchemas, options));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Timestamps
|
|
248
|
+
if (schema.options?.timestamps !== false) {
|
|
249
|
+
properties.push({ name: 'created_at', type: 'DateTimeString', optional: true, readonly: false }, { name: 'updated_at', type: 'DateTimeString', optional: true, readonly: false });
|
|
250
|
+
}
|
|
251
|
+
// Soft delete
|
|
252
|
+
if (schema.options?.softDelete) {
|
|
253
|
+
properties.push({ name: 'deleted_at', type: 'DateTimeString', optional: true, readonly: false });
|
|
254
|
+
}
|
|
255
|
+
// Collect dependencies
|
|
256
|
+
const dependencySet = new Set();
|
|
257
|
+
for (const prop of properties) {
|
|
258
|
+
for (const ref of extractTypeReferences(prop.type, allSchemaNames)) {
|
|
259
|
+
if (ref !== schema.name) {
|
|
260
|
+
dependencySet.add(ref);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Collect enum dependencies (EnumRef properties + compound enum fields)
|
|
265
|
+
const enumDependencySet = new Set();
|
|
266
|
+
if (schema.properties) {
|
|
267
|
+
for (const property of Object.values(schema.properties)) {
|
|
268
|
+
if (property.type === 'EnumRef' && typeof property.enum === 'string') {
|
|
269
|
+
enumDependencySet.add(property.enum);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Check expanded properties for enum references
|
|
274
|
+
if (schema.expandedProperties) {
|
|
275
|
+
for (const expanded of Object.values(schema.expandedProperties)) {
|
|
276
|
+
const compoundDef = options.customTypes.compound[expanded.sourceType];
|
|
277
|
+
if (!compoundDef)
|
|
278
|
+
continue;
|
|
279
|
+
for (const col of expanded.columns) {
|
|
280
|
+
if (col.type === 'Enum') {
|
|
281
|
+
const compoundField = compoundDef.fields.find(f => f.suffix === col.suffix);
|
|
282
|
+
if (compoundField?.enumRef) {
|
|
283
|
+
enumDependencySet.add(compoundField.enumRef);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Resolve display name
|
|
290
|
+
const displayName = resolveString(schema.displayName, options.defaultLocale, options.fallbackLocale);
|
|
291
|
+
return {
|
|
292
|
+
name: schema.name,
|
|
293
|
+
properties,
|
|
294
|
+
comment: displayName ?? schema.name,
|
|
295
|
+
dependencies: dependencySet.size > 0 ? Array.from(dependencySet).sort() : undefined,
|
|
296
|
+
enumDependencies: enumDependencySet.size > 0 ? Array.from(enumDependencySet).sort() : undefined,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/** Format a TypeScript property line. */
|
|
300
|
+
export function formatProperty(property) {
|
|
301
|
+
const optional = property.optional ? '?' : '';
|
|
302
|
+
return ` ${property.name}${optional}: ${property.type};`;
|
|
303
|
+
}
|
|
304
|
+
/** Format a TypeScript interface. */
|
|
305
|
+
export function formatInterface(iface) {
|
|
306
|
+
const comment = iface.comment ? `/**\n * ${iface.comment}\n */\n` : '';
|
|
307
|
+
const properties = iface.properties.map(formatProperty).join('\n');
|
|
308
|
+
return `${comment}export interface ${iface.name} {\n${properties}\n}`;
|
|
309
|
+
}
|
|
310
|
+
/** Generate interfaces for all non-enum, non-hidden schemas. */
|
|
311
|
+
export function generateInterfaces(schemas, options) {
|
|
312
|
+
const interfaces = [];
|
|
313
|
+
for (const schema of Object.values(schemas)) {
|
|
314
|
+
if (schema.kind === 'enum')
|
|
315
|
+
continue;
|
|
316
|
+
if (schema.options?.hidden === true)
|
|
317
|
+
continue;
|
|
318
|
+
interfaces.push(schemaToInterface(schema, schemas, options));
|
|
319
|
+
}
|
|
320
|
+
return interfaces;
|
|
321
|
+
}
|
|
322
|
+
/** Resolve a LocalizedString to a single string. */
|
|
323
|
+
function resolveString(value, defaultLocale, fallbackLocale) {
|
|
324
|
+
if (value === undefined)
|
|
325
|
+
return undefined;
|
|
326
|
+
if (typeof value === 'string')
|
|
327
|
+
return value;
|
|
328
|
+
return value[defaultLocale] ?? value[fallbackLocale] ?? value['en'] ?? Object.values(value)[0];
|
|
329
|
+
}
|
|
330
|
+
/** Check if interface uses DateTimeString or DateString types. */
|
|
331
|
+
export function needsDateTimeImports(iface) {
|
|
332
|
+
let dateTime = false;
|
|
333
|
+
let date = false;
|
|
334
|
+
for (const prop of iface.properties) {
|
|
335
|
+
if (prop.type === 'DateTimeString' || prop.type.includes('DateTimeString'))
|
|
336
|
+
dateTime = true;
|
|
337
|
+
if (prop.type === 'DateString' || prop.type.includes('DateString'))
|
|
338
|
+
date = true;
|
|
339
|
+
}
|
|
340
|
+
return { dateTime, date };
|
|
341
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of BaseModelGenerator.php — generates abstract BaseModel with morph map.
|
|
3
|
+
*/
|
|
4
|
+
import { SchemaReader } from './schema-reader.js';
|
|
5
|
+
import type { GeneratedFile, PhpConfig } from './types.js';
|
|
6
|
+
/** Generate the abstract BaseModel class with morph map. */
|
|
7
|
+
export declare function generateBaseModel(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of BaseModelGenerator.php — generates abstract BaseModel with morph map.
|
|
3
|
+
*/
|
|
4
|
+
import { toPascalCase } from './naming-helper.js';
|
|
5
|
+
import { baseFile } from './types.js';
|
|
6
|
+
/** Generate the abstract BaseModel class with morph map. */
|
|
7
|
+
export function generateBaseModel(reader, config) {
|
|
8
|
+
const baseNamespace = config.models.baseNamespace;
|
|
9
|
+
const modelNamespace = config.models.namespace;
|
|
10
|
+
// Build morph map entries from all visible object schemas
|
|
11
|
+
const morphMap = {};
|
|
12
|
+
for (const name of Object.keys(reader.getVisibleObjectSchemas())) {
|
|
13
|
+
const modelName = toPascalCase(name);
|
|
14
|
+
morphMap[modelName] = `\\${modelNamespace}\\${modelName}::class`;
|
|
15
|
+
}
|
|
16
|
+
// Sort for consistent output
|
|
17
|
+
const sortedKeys = Object.keys(morphMap).sort();
|
|
18
|
+
const morphEntries = sortedKeys
|
|
19
|
+
.map(k => ` '${k}' => ${morphMap[k]},`)
|
|
20
|
+
.join('\n');
|
|
21
|
+
const content = `<?php
|
|
22
|
+
|
|
23
|
+
namespace ${baseNamespace};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base model class for all Omnify-generated models.
|
|
27
|
+
* Contains model mapping for polymorphic relations.
|
|
28
|
+
*
|
|
29
|
+
* DO NOT EDIT - This file is auto-generated by Omnify.
|
|
30
|
+
* Any changes will be overwritten on next generation.
|
|
31
|
+
*
|
|
32
|
+
* @generated by omnify
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
use Illuminate\\Database\\Eloquent\\Model;
|
|
36
|
+
use Illuminate\\Database\\Eloquent\\Relations\\Relation;
|
|
37
|
+
|
|
38
|
+
abstract class BaseModel extends Model
|
|
39
|
+
{
|
|
40
|
+
/**
|
|
41
|
+
* Model class map for polymorphic relations.
|
|
42
|
+
*/
|
|
43
|
+
protected static array $modelMap = [
|
|
44
|
+
${morphEntries}
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Boot the model and register morph map.
|
|
49
|
+
*/
|
|
50
|
+
protected static function boot(): void
|
|
51
|
+
{
|
|
52
|
+
parent::boot();
|
|
53
|
+
|
|
54
|
+
// Register morph map for polymorphic relations
|
|
55
|
+
Relation::enforceMorphMap(static::$modelMap);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the model class for a given morph type.
|
|
60
|
+
*/
|
|
61
|
+
public static function getModelClass(string $morphType): ?string
|
|
62
|
+
{
|
|
63
|
+
return static::$modelMap[$morphType] ?? null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
return [
|
|
68
|
+
baseFile(`${config.models.basePath}/BaseModel.php`, content),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of FactoryGenerator.php — generates Model Factory classes.
|
|
3
|
+
*/
|
|
4
|
+
import { SchemaReader } from './schema-reader.js';
|
|
5
|
+
import type { GeneratedFile, PhpConfig } from './types.js';
|
|
6
|
+
/** Generate Factory classes for all visible object schemas. */
|
|
7
|
+
export declare function generateFactories(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of FactoryGenerator.php — generates Model Factory classes.
|
|
3
|
+
*/
|
|
4
|
+
import { toPascalCase } from './naming-helper.js';
|
|
5
|
+
import { toFaker, compoundFaker, associationFaker } from './faker-mapper.js';
|
|
6
|
+
import { userFile } from './types.js';
|
|
7
|
+
/** Generate Factory classes for all visible object schemas. */
|
|
8
|
+
export function generateFactories(reader, config) {
|
|
9
|
+
const files = [];
|
|
10
|
+
const schemas = reader.getSchemas();
|
|
11
|
+
for (const [name, schema] of Object.entries(reader.getVisibleObjectSchemas())) {
|
|
12
|
+
const file = generateFactory(name, schema, reader, schemas, config);
|
|
13
|
+
if (file)
|
|
14
|
+
files.push(file);
|
|
15
|
+
}
|
|
16
|
+
return files;
|
|
17
|
+
}
|
|
18
|
+
function generateFactory(name, schema, reader, schemas, config) {
|
|
19
|
+
const modelName = toPascalCase(name);
|
|
20
|
+
const modelNamespace = config.models.namespace;
|
|
21
|
+
const factoryNamespace = config.factories.namespace;
|
|
22
|
+
const properties = (schema.properties ?? {});
|
|
23
|
+
const expandedProperties = reader.getExpandedProperties(name);
|
|
24
|
+
const propertyOrder = reader.getPropertyOrder(name);
|
|
25
|
+
const attributes = [];
|
|
26
|
+
const imports = [];
|
|
27
|
+
for (const propName of propertyOrder) {
|
|
28
|
+
const prop = properties[propName];
|
|
29
|
+
if (!prop)
|
|
30
|
+
continue;
|
|
31
|
+
const type = prop['type'] ?? 'String';
|
|
32
|
+
// Handle associations (foreign keys)
|
|
33
|
+
if (type === 'Association') {
|
|
34
|
+
const result = associationFaker(propName, prop, modelNamespace, name);
|
|
35
|
+
if (result) {
|
|
36
|
+
attributes.push(result.fake);
|
|
37
|
+
if (result.import)
|
|
38
|
+
imports.push(result.import);
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Handle compound types
|
|
43
|
+
if (expandedProperties[propName]) {
|
|
44
|
+
const expansion = expandedProperties[propName];
|
|
45
|
+
const sourceType = expansion.sourceType ?? '';
|
|
46
|
+
const fakes = compoundFaker(propName, expansion, sourceType);
|
|
47
|
+
attributes.push(...fakes);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Handle regular properties
|
|
51
|
+
const fake = toFaker(propName, prop, schemas);
|
|
52
|
+
if (fake)
|
|
53
|
+
attributes.push(fake);
|
|
54
|
+
}
|
|
55
|
+
const attributesStr = attributes.length > 0
|
|
56
|
+
? attributes.map(a => ` ${a}`).join('\n')
|
|
57
|
+
: '';
|
|
58
|
+
const uniqueImports = [...new Set(imports)];
|
|
59
|
+
const importsStr = uniqueImports.length > 0
|
|
60
|
+
? '\n' + uniqueImports.join('\n')
|
|
61
|
+
: '';
|
|
62
|
+
const content = `<?php
|
|
63
|
+
|
|
64
|
+
namespace ${factoryNamespace};
|
|
65
|
+
|
|
66
|
+
use ${modelNamespace}\\${modelName};
|
|
67
|
+
use Illuminate\\Database\\Eloquent\\Factories\\Factory;
|
|
68
|
+
${importsStr}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* ${modelName} Factory
|
|
72
|
+
*
|
|
73
|
+
* SAFE TO EDIT - This file is never overwritten by Omnify.
|
|
74
|
+
*
|
|
75
|
+
* @extends Factory<${modelName}>
|
|
76
|
+
*/
|
|
77
|
+
class ${modelName}Factory extends Factory
|
|
78
|
+
{
|
|
79
|
+
protected $model = ${modelName}::class;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Define the model's default state.
|
|
83
|
+
*
|
|
84
|
+
* @return array<string, mixed>
|
|
85
|
+
*/
|
|
86
|
+
public function definition(): array
|
|
87
|
+
{
|
|
88
|
+
return [
|
|
89
|
+
${attributesStr}
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
return userFile(`${config.factories.path}/${modelName}Factory.php`, content);
|
|
95
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of FakerMapper.php — generates Faker expressions for factories.
|
|
3
|
+
*/
|
|
4
|
+
/** Generate faker expression for a property. */
|
|
5
|
+
export declare function toFaker(propName: string, property: Record<string, unknown>, schemas?: Record<string, Record<string, unknown>>): string | null;
|
|
6
|
+
/** Generate faker expressions for compound type expanded columns. */
|
|
7
|
+
export declare function compoundFaker(propName: string, expansion: Record<string, unknown>, sourceType: string): string[];
|
|
8
|
+
/** Generate faker for association (foreign key). */
|
|
9
|
+
export declare function associationFaker(propName: string, property: Record<string, unknown>, modelNamespace: string, currentSchema: string): {
|
|
10
|
+
fake: string;
|
|
11
|
+
import: string | null;
|
|
12
|
+
} | null;
|