@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.
Files changed (47) hide show
  1. package/dist/cli.d.ts +13 -0
  2. package/dist/cli.js +180 -0
  3. package/dist/enum-generator.d.ts +28 -0
  4. package/dist/enum-generator.js +253 -0
  5. package/dist/generator.d.ts +19 -0
  6. package/dist/generator.js +330 -0
  7. package/dist/i18n-generator.d.ts +10 -0
  8. package/dist/i18n-generator.js +143 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.js +9 -0
  11. package/dist/interface-generator.d.ts +31 -0
  12. package/dist/interface-generator.js +341 -0
  13. package/dist/php/base-model-generator.d.ts +7 -0
  14. package/dist/php/base-model-generator.js +70 -0
  15. package/dist/php/factory-generator.d.ts +7 -0
  16. package/dist/php/factory-generator.js +95 -0
  17. package/dist/php/faker-mapper.d.ts +12 -0
  18. package/dist/php/faker-mapper.js +206 -0
  19. package/dist/php/index.d.ts +18 -0
  20. package/dist/php/index.js +40 -0
  21. package/dist/php/locales-generator.d.ts +7 -0
  22. package/dist/php/locales-generator.js +135 -0
  23. package/dist/php/model-generator.d.ts +7 -0
  24. package/dist/php/model-generator.js +396 -0
  25. package/dist/php/naming-helper.d.ts +22 -0
  26. package/dist/php/naming-helper.js +61 -0
  27. package/dist/php/relation-builder.d.ts +12 -0
  28. package/dist/php/relation-builder.js +147 -0
  29. package/dist/php/request-generator.d.ts +7 -0
  30. package/dist/php/request-generator.js +221 -0
  31. package/dist/php/resource-generator.d.ts +7 -0
  32. package/dist/php/resource-generator.js +178 -0
  33. package/dist/php/schema-reader.d.ts +28 -0
  34. package/dist/php/schema-reader.js +79 -0
  35. package/dist/php/service-provider-generator.d.ts +7 -0
  36. package/dist/php/service-provider-generator.js +64 -0
  37. package/dist/php/trait-generator.d.ts +6 -0
  38. package/dist/php/trait-generator.js +104 -0
  39. package/dist/php/type-mapper.d.ts +25 -0
  40. package/dist/php/type-mapper.js +217 -0
  41. package/dist/php/types.d.ts +61 -0
  42. package/dist/php/types.js +68 -0
  43. package/dist/types.d.ts +196 -0
  44. package/dist/types.js +6 -0
  45. package/dist/zod-generator.d.ts +32 -0
  46. package/dist/zod-generator.js +428 -0
  47. 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;