@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,206 @@
1
+ /**
2
+ * Port of FakerMapper.php — generates Faker expressions for factories.
3
+ */
4
+ import { toSnakeCase } from './naming-helper.js';
5
+ import { resolveEnumValues } from './type-mapper.js';
6
+ /** Generate faker expression for a property. */
7
+ export function toFaker(propName, property, schemas = {}) {
8
+ const type = property['type'] ?? 'String';
9
+ const snakeName = toSnakeCase(propName);
10
+ if (['deleted_at', 'created_at', 'updated_at'].includes(propName))
11
+ return null;
12
+ if (type === 'Association')
13
+ return null;
14
+ switch (type) {
15
+ case 'String': return stringFaker(propName, snakeName, property);
16
+ case 'Email': return `'${snakeName}' => fake()->unique()->safeEmail(),`;
17
+ case 'Password': return `'${snakeName}' => bcrypt('password'),`;
18
+ case 'Int':
19
+ case 'BigInt': return intFaker(snakeName, property);
20
+ case 'TinyInt': return `'${snakeName}' => fake()->numberBetween(0, 127),`;
21
+ case 'Float':
22
+ case 'Decimal': return `'${snakeName}' => fake()->randomFloat(2, 1, 10000),`;
23
+ case 'Boolean': return `'${snakeName}' => fake()->boolean(),`;
24
+ case 'Text': return `'${snakeName}' => fake()->paragraphs(3, true),`;
25
+ case 'MediumText': return `'${snakeName}' => fake()->paragraphs(3, true),`;
26
+ case 'LongText': return `'${snakeName}' => fake()->paragraphs(5, true),`;
27
+ case 'Date': return `'${snakeName}' => fake()->date(),`;
28
+ case 'Time': return `'${snakeName}' => fake()->time(),`;
29
+ case 'DateTime':
30
+ case 'Timestamp': return `'${snakeName}' => fake()->dateTime(),`;
31
+ case 'Json': return `'${snakeName}' => [],`;
32
+ case 'Uuid': return `'${snakeName}' => (string) \\Illuminate\\Support\\Str::uuid(),`;
33
+ case 'Enum': return enumFaker(snakeName, property);
34
+ case 'EnumRef': return enumRefFaker(snakeName, property, schemas);
35
+ case 'JapanesePhone': return `'${snakeName}' => fake('ja_JP')->phoneNumber(),`;
36
+ case 'JapanesePostalCode': return `'${snakeName}' => fake('ja_JP')->postcode(),`;
37
+ default: return `'${snakeName}' => fake()->sentence(),`;
38
+ }
39
+ }
40
+ /** Generate faker expressions for compound type expanded columns. */
41
+ export function compoundFaker(propName, expansion, sourceType) {
42
+ const snakeName = toSnakeCase(propName);
43
+ const jaFaker = "fake('ja_JP')";
44
+ switch (sourceType) {
45
+ case 'JapaneseName':
46
+ return [
47
+ `'${snakeName}_lastname' => ${jaFaker}->lastName(),`,
48
+ `'${snakeName}_firstname' => ${jaFaker}->firstName(),`,
49
+ `'${snakeName}_kana_lastname' => ${jaFaker}->lastKanaName(),`,
50
+ `'${snakeName}_kana_firstname' => ${jaFaker}->firstKanaName(),`,
51
+ ];
52
+ case 'JapaneseAddress':
53
+ return japaneseAddressFaker(snakeName, expansion);
54
+ case 'JapaneseBankAccount':
55
+ return [
56
+ `'${snakeName}_bank_code' => ${jaFaker}->regexify('[0-9]{4}'),`,
57
+ `'${snakeName}_branch_code' => ${jaFaker}->regexify('[0-9]{3}'),`,
58
+ `'${snakeName}_account_type' => ${jaFaker}->randomElement(['1', '2', '4']),`,
59
+ `'${snakeName}_account_number' => ${jaFaker}->regexify('[0-9]{7}'),`,
60
+ `'${snakeName}_account_holder' => ${jaFaker}->lastKanaName() . ' ' . ${jaFaker}->firstKanaName(),`,
61
+ ];
62
+ default:
63
+ return genericCompoundFaker(snakeName, expansion);
64
+ }
65
+ }
66
+ /** Generate faker for association (foreign key). */
67
+ export function associationFaker(propName, property, modelNamespace, currentSchema) {
68
+ const relation = property['relation'] ?? '';
69
+ const target = property['target'] ?? '';
70
+ if (relation !== 'ManyToOne' || !target)
71
+ return null;
72
+ const snakeName = toSnakeCase(propName);
73
+ const foreignKey = `${snakeName}_id`;
74
+ const nullable = property['nullable'] ?? false;
75
+ const fake = nullable
76
+ ? `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id,`
77
+ : `'${foreignKey}' => ${target}::query()->inRandomOrder()->first()?->id ?? ${target}::factory()->create()->id,`;
78
+ let importLine = null;
79
+ if (target !== currentSchema) {
80
+ const targetNs = property['targetNamespace'] ?? modelNamespace;
81
+ importLine = `use ${targetNs}\\${target};`;
82
+ }
83
+ return { fake, import: importLine };
84
+ }
85
+ function stringFaker(propName, snakeName, property) {
86
+ const lower = propName.toLowerCase();
87
+ if (propName === 'slug')
88
+ return `'${snakeName}' => fake()->unique()->slug(2),`;
89
+ if (propName === 'uuid' || propName === 'uid')
90
+ return `'${snakeName}' => (string) \\Illuminate\\Support\\Str::uuid(),`;
91
+ if (lower.includes('email'))
92
+ return `'${snakeName}' => fake()->unique()->safeEmail(),`;
93
+ if (lower.includes('phone'))
94
+ return `'${snakeName}' => fake()->phoneNumber(),`;
95
+ if (lower.includes('image') || lower.includes('photo') || lower.includes('avatar'))
96
+ return `'${snakeName}' => fake()->imageUrl(),`;
97
+ if (lower.includes('url') || lower.includes('website'))
98
+ return `'${snakeName}' => fake()->url(),`;
99
+ if (lower.includes('path') || lower.includes('file'))
100
+ return `'${snakeName}' => 'uploads/' . fake()->uuid() . '.jpg',`;
101
+ if (propName === 'name' || propName === 'title')
102
+ return `'${snakeName}' => fake()->sentence(3),`;
103
+ if (lower.includes('name'))
104
+ return `'${snakeName}' => fake()->name(),`;
105
+ if (lower.includes('address'))
106
+ return `'${snakeName}' => fake()->address(),`;
107
+ if (lower.includes('city'))
108
+ return `'${snakeName}' => fake()->city(),`;
109
+ if (lower.includes('country'))
110
+ return `'${snakeName}' => fake()->country(),`;
111
+ if (lower.includes('zip') || lower.includes('postal'))
112
+ return `'${snakeName}' => fake()->postcode(),`;
113
+ if (lower.includes('color'))
114
+ return `'${snakeName}' => fake()->hexColor(),`;
115
+ if (lower.includes('token') || lower.includes('secret') || lower.includes('key'))
116
+ return `'${snakeName}' => \\Illuminate\\Support\\Str::random(32),`;
117
+ if (lower.includes('code'))
118
+ return `'${snakeName}' => fake()->unique()->regexify('[A-Z0-9]{8}'),`;
119
+ const length = property['length'];
120
+ if (length != null && length <= 50)
121
+ return `'${snakeName}' => fake()->words(3, true),`;
122
+ return `'${snakeName}' => fake()->sentence(),`;
123
+ }
124
+ function intFaker(snakeName, property) {
125
+ const isUnique = property['unique'] ?? false;
126
+ const up = isUnique ? 'unique()->' : '';
127
+ if (snakeName.includes('count') || snakeName.includes('quantity'))
128
+ return `'${snakeName}' => fake()->${up}numberBetween(0, 100),`;
129
+ if (snakeName.includes('price') || snakeName.includes('amount') || snakeName.includes('cost'))
130
+ return `'${snakeName}' => fake()->${up}numberBetween(100, 10000),`;
131
+ if (snakeName.includes('order') || snakeName.includes('sort') || snakeName.includes('position'))
132
+ return `'${snakeName}' => fake()->${up}numberBetween(1, 100),`;
133
+ if (snakeName.includes('age'))
134
+ return `'${snakeName}' => fake()->${up}numberBetween(18, 80),`;
135
+ if (snakeName.includes('year'))
136
+ return `'${snakeName}' => fake()->${up}year(),`;
137
+ const range = isUnique ? '1, 1000000' : '1, 1000';
138
+ return `'${snakeName}' => fake()->${up}numberBetween(${range}),`;
139
+ }
140
+ function enumFaker(snakeName, property) {
141
+ const values = resolveEnumValues(property);
142
+ if (!values)
143
+ return `'${snakeName}' => null,`;
144
+ const valuesStr = values.map(v => `'${v}'`).join(', ');
145
+ return `'${snakeName}' => fake()->randomElement([${valuesStr}]),`;
146
+ }
147
+ function enumRefFaker(snakeName, property, schemas) {
148
+ const enumName = property['enum'];
149
+ if (!enumName || typeof enumName !== 'string')
150
+ return `'${snakeName}' => null,`;
151
+ const enumSchema = schemas[enumName];
152
+ if (!enumSchema || enumSchema['kind'] !== 'enum' || !Array.isArray(enumSchema['values']))
153
+ return `'${snakeName}' => null,`;
154
+ const values = [];
155
+ for (const v of enumSchema['values']) {
156
+ if (typeof v === 'string')
157
+ values.push(v);
158
+ else if (typeof v === 'object' && v !== null && 'value' in v)
159
+ values.push(v.value);
160
+ }
161
+ if (values.length === 0)
162
+ return `'${snakeName}' => null,`;
163
+ const valuesStr = values.map(v => `'${v}'`).join(', ');
164
+ return `'${snakeName}' => fake()->randomElement([${valuesStr}]),`;
165
+ }
166
+ function japaneseAddressFaker(snakeName, expansion) {
167
+ const jaFaker = "fake('ja_JP')";
168
+ const lines = [`'${snakeName}_postal_code' => ${jaFaker}->postcode(),`];
169
+ // Check for prefecture enum values
170
+ let prefectureValues = null;
171
+ const columns = expansion['columns'] ?? [];
172
+ for (const col of columns) {
173
+ if (col['suffix'] === 'Prefecture' && col['enum']) {
174
+ const enumDef = col['enum'];
175
+ if (Array.isArray(enumDef['values'])) {
176
+ prefectureValues = enumDef['values'];
177
+ }
178
+ }
179
+ }
180
+ if (prefectureValues) {
181
+ const keys = prefectureValues.map(v => `'${typeof v === 'string' ? v : (v['value'] ?? v)}'`).join(', ');
182
+ lines.push(`'${snakeName}_prefecture' => fake()->randomElement([${keys}]),`);
183
+ }
184
+ else {
185
+ lines.push(`'${snakeName}_prefecture' => fake()->randomElement(['hokkaido', 'aomori', 'iwate', 'miyagi', 'akita', 'yamagata', 'fukushima', 'ibaraki', 'tochigi', 'gunma', 'saitama', 'chiba', 'tokyo', 'kanagawa', 'niigata', 'toyama', 'ishikawa', 'fukui', 'yamanashi', 'nagano', 'gifu', 'shizuoka', 'aichi', 'mie', 'shiga', 'kyoto', 'osaka', 'hyogo', 'nara', 'wakayama', 'tottori', 'shimane', 'okayama', 'hiroshima', 'yamaguchi', 'tokushima', 'kagawa', 'ehime', 'kochi', 'fukuoka', 'saga', 'nagasaki', 'kumamoto', 'oita', 'miyazaki', 'kagoshima', 'okinawa']),`);
186
+ }
187
+ lines.push(`'${snakeName}_address1' => ${jaFaker}->city() . ${jaFaker}->ward(),`);
188
+ lines.push(`'${snakeName}_address2' => ${jaFaker}->streetAddress(),`);
189
+ lines.push(`'${snakeName}_address3' => ${jaFaker}->optional()->secondaryAddress(),`);
190
+ return lines;
191
+ }
192
+ function genericCompoundFaker(snakeName, expansion) {
193
+ const lines = [];
194
+ const columns = expansion['columns'] ?? [];
195
+ for (const col of columns) {
196
+ const colName = col['name'] ?? `${snakeName}_${toSnakeCase(col['suffix'] ?? '')}`;
197
+ const colType = col['type'] ?? 'String';
198
+ if (['Int', 'BigInt', 'TinyInt'].includes(colType)) {
199
+ lines.push(`'${colName}' => fake()->numberBetween(1, 100),`);
200
+ }
201
+ else {
202
+ lines.push(`'${colName}' => fake()->words(2, true),`);
203
+ }
204
+ }
205
+ return lines;
206
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * PHP code generation orchestrator.
3
+ *
4
+ * Reads schemas.json and generates all Laravel PHP files:
5
+ * - BaseModel, Models
6
+ * - Form Requests (Store + Update)
7
+ * - API Resources
8
+ * - Factories
9
+ * - Traits
10
+ * - Locales
11
+ * - OmnifyServiceProvider
12
+ */
13
+ import type { SchemasJson } from '../types.js';
14
+ import type { GeneratedFile, LaravelCodegenOverrides } from './types.js';
15
+ export type { GeneratedFile, PhpConfig, LaravelCodegenOverrides } from './types.js';
16
+ export { derivePhpConfig } from './types.js';
17
+ /** Generate all PHP files from schemas.json data. */
18
+ export declare function generatePhp(data: SchemasJson, overrides?: LaravelCodegenOverrides): GeneratedFile[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * PHP code generation orchestrator.
3
+ *
4
+ * Reads schemas.json and generates all Laravel PHP files:
5
+ * - BaseModel, Models
6
+ * - Form Requests (Store + Update)
7
+ * - API Resources
8
+ * - Factories
9
+ * - Traits
10
+ * - Locales
11
+ * - OmnifyServiceProvider
12
+ */
13
+ import { derivePhpConfig } from './types.js';
14
+ import { SchemaReader } from './schema-reader.js';
15
+ import { generateBaseModel } from './base-model-generator.js';
16
+ import { generateTrait } from './trait-generator.js';
17
+ import { generateLocales } from './locales-generator.js';
18
+ import { generateModels } from './model-generator.js';
19
+ import { generateRequests } from './request-generator.js';
20
+ import { generateResources } from './resource-generator.js';
21
+ import { generateFactories } from './factory-generator.js';
22
+ import { generateServiceProvider } from './service-provider-generator.js';
23
+ export { derivePhpConfig } from './types.js';
24
+ /** Generate all PHP files from schemas.json data. */
25
+ export function generatePhp(data, overrides) {
26
+ const reader = SchemaReader.fromData(data);
27
+ const config = derivePhpConfig(overrides);
28
+ const files = [];
29
+ // Infrastructure files
30
+ files.push(...generateBaseModel(reader, config));
31
+ files.push(...generateTrait(config));
32
+ files.push(...generateServiceProvider(reader, config));
33
+ // Per-schema files
34
+ files.push(...generateLocales(reader, config));
35
+ files.push(...generateModels(reader, config));
36
+ files.push(...generateRequests(reader, config));
37
+ files.push(...generateResources(reader, config));
38
+ files.push(...generateFactories(reader, config));
39
+ return files;
40
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Port of LocalesGenerator.php — generates Locales classes with display name constants.
3
+ */
4
+ import { SchemaReader } from './schema-reader.js';
5
+ import type { GeneratedFile, PhpConfig } from './types.js';
6
+ /** Generate Locales classes for all object schemas. */
7
+ export declare function generateLocales(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Port of LocalesGenerator.php — generates Locales classes with display name constants.
3
+ */
4
+ import { toPascalCase, toSnakeCase } from './naming-helper.js';
5
+ import { baseFile } from './types.js';
6
+ /** Generate Locales classes for all object schemas. */
7
+ export function generateLocales(reader, config) {
8
+ const files = [];
9
+ for (const [name, schema] of Object.entries(reader.getObjectSchemas())) {
10
+ const file = generateLocalesClass(name, schema, reader, config);
11
+ if (file)
12
+ files.push(file);
13
+ }
14
+ return files;
15
+ }
16
+ function generateLocalesClass(name, schema, reader, config) {
17
+ const modelName = toPascalCase(name);
18
+ const baseNamespace = config.models.baseNamespace;
19
+ const displayNames = buildDisplayNames(schema);
20
+ const propertyDisplayNames = buildPropertyDisplayNames(schema, reader, name);
21
+ const displayNamesPhp = formatArrayConstant(displayNames, 8);
22
+ const propertyDisplayNamesPhp = formatNestedArrayConstant(propertyDisplayNames, 8);
23
+ const content = `<?php
24
+
25
+ namespace ${baseNamespace}\\Locales;
26
+
27
+ /**
28
+ * Localized display names for ${modelName}.
29
+ *
30
+ * DO NOT EDIT - This file is auto-generated by Omnify.
31
+ * Any changes will be overwritten on next generation.
32
+ *
33
+ * @generated by omnify
34
+ */
35
+ class ${modelName}Locales
36
+ {
37
+ /**
38
+ * Localized display names for the model.
39
+ *
40
+ * @var array<string, string>
41
+ */
42
+ public const DISPLAY_NAMES = ${displayNamesPhp};
43
+
44
+ /**
45
+ * Localized display names for properties.
46
+ *
47
+ * @var array<string, array<string, string>>
48
+ */
49
+ public const PROPERTY_DISPLAY_NAMES = ${propertyDisplayNamesPhp};
50
+ }
51
+ `;
52
+ return baseFile(`${config.models.basePath}/Locales/${modelName}Locales.php`, content);
53
+ }
54
+ function buildDisplayNames(schema) {
55
+ const displayName = schema.displayName;
56
+ if (displayName == null)
57
+ return { en: schema.name ?? '' };
58
+ if (typeof displayName === 'string')
59
+ return { en: displayName };
60
+ if (typeof displayName === 'object')
61
+ return displayName;
62
+ return { en: schema.name ?? '' };
63
+ }
64
+ function buildPropertyDisplayNames(schema, reader, schemaName) {
65
+ const result = {};
66
+ const properties = schema.properties ?? {};
67
+ const expandedProperties = reader.getExpandedProperties(schemaName);
68
+ const propertyOrder = reader.getPropertyOrder(schemaName);
69
+ for (const propName of propertyOrder) {
70
+ const prop = properties[propName];
71
+ if (!prop)
72
+ continue;
73
+ const type = prop.type ?? 'String';
74
+ if (type === 'Association')
75
+ continue;
76
+ // Compound type: use column-level displayNames
77
+ if (expandedProperties[propName]) {
78
+ const expansion = expandedProperties[propName];
79
+ for (const col of expansion.columns ?? []) {
80
+ const colAny = col;
81
+ const colName = colAny['name'] ?? '';
82
+ const colDisplayName = colAny['displayName'];
83
+ if (colDisplayName) {
84
+ result[colName] = typeof colDisplayName === 'string'
85
+ ? { en: colDisplayName }
86
+ : colDisplayName;
87
+ }
88
+ }
89
+ continue;
90
+ }
91
+ // Regular property
92
+ const displayName = prop.displayName;
93
+ const snakeName = toSnakeCase(propName);
94
+ if (displayName == null)
95
+ continue;
96
+ if (typeof displayName === 'string') {
97
+ result[snakeName] = { en: displayName };
98
+ }
99
+ else if (typeof displayName === 'object') {
100
+ result[snakeName] = displayName;
101
+ }
102
+ }
103
+ return result;
104
+ }
105
+ function formatArrayConstant(data, indent) {
106
+ const entries = Object.entries(data);
107
+ if (entries.length === 0)
108
+ return '[]';
109
+ const pad = ' '.repeat(indent);
110
+ const lines = ['['];
111
+ for (const [key, value] of entries) {
112
+ const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
113
+ lines.push(`${pad}'${key}' => '${escaped}',`);
114
+ }
115
+ lines.push(' '.repeat(indent - 4) + ']');
116
+ return lines.join('\n');
117
+ }
118
+ function formatNestedArrayConstant(data, indent) {
119
+ const entries = Object.entries(data);
120
+ if (entries.length === 0)
121
+ return '[]';
122
+ const pad = ' '.repeat(indent);
123
+ const innerPad = ' '.repeat(indent + 4);
124
+ const lines = ['['];
125
+ for (const [propName, locales] of entries) {
126
+ lines.push(`${pad}'${propName}' => [`);
127
+ for (const [locale, value] of Object.entries(locales)) {
128
+ const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
129
+ lines.push(`${innerPad}'${locale}' => '${escaped}',`);
130
+ }
131
+ lines.push(`${pad}],`);
132
+ }
133
+ lines.push(' '.repeat(indent - 4) + ']');
134
+ return lines.join('\n');
135
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Port of ModelGenerator.php — generates Eloquent model base + user classes.
3
+ */
4
+ import { SchemaReader } from './schema-reader.js';
5
+ import type { GeneratedFile, PhpConfig } from './types.js';
6
+ /** Generate base model and user model for all object schemas. */
7
+ export declare function generateModels(reader: SchemaReader, config: PhpConfig): GeneratedFile[];