@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,32 @@
1
+ /**
2
+ * @omnify/ts — Zod Schema Generator
3
+ *
4
+ * Generates Zod schemas alongside TypeScript interfaces for form validation.
5
+ */
6
+ import type { SchemaDefinition, ZodPropertySchema, SchemaDisplayNames, GeneratorOptions } from './types.js';
7
+ /**
8
+ * Generate Zod schemas for all properties in a schema.
9
+ */
10
+ export declare function generateZodSchemas(schema: SchemaDefinition, options: GeneratorOptions): ZodPropertySchema[];
11
+ /**
12
+ * Generate display names and placeholders for a schema.
13
+ */
14
+ export declare function generateDisplayNames(schema: SchemaDefinition, options: GeneratorOptions): SchemaDisplayNames;
15
+ /**
16
+ * Get fields to exclude from create/update schemas.
17
+ */
18
+ export declare function getExcludedFields(schema: SchemaDefinition): {
19
+ create: Set<string>;
20
+ update: Set<string>;
21
+ };
22
+ /**
23
+ * Format Zod schemas section for a base file.
24
+ */
25
+ export declare function formatZodSchemasSection(schemaName: string, zodSchemas: ZodPropertySchema[], displayNames: SchemaDisplayNames, excludedFields: {
26
+ create: Set<string>;
27
+ update: Set<string>;
28
+ }): string;
29
+ /**
30
+ * Format user model file with Zod re-exports.
31
+ */
32
+ export declare function formatZodModelFile(schemaName: string): string;
@@ -0,0 +1,428 @@
1
+ /**
2
+ * @omnify/ts — Zod Schema Generator
3
+ *
4
+ * Generates Zod schemas alongside TypeScript interfaces for form validation.
5
+ */
6
+ import { toSnakeCase } from './interface-generator.js';
7
+ /**
8
+ * Get localized display name as multi-locale object.
9
+ */
10
+ function getMultiLocaleDisplayName(value, locales, fallbackLocale, defaultValue) {
11
+ if (!value) {
12
+ const result = {};
13
+ for (const locale of locales)
14
+ result[locale] = defaultValue;
15
+ return result;
16
+ }
17
+ if (typeof value === 'string') {
18
+ const result = {};
19
+ for (const locale of locales)
20
+ result[locale] = value;
21
+ return result;
22
+ }
23
+ const result = {};
24
+ for (const locale of locales) {
25
+ result[locale] = value[locale] ?? value[fallbackLocale] ?? value['en'] ?? defaultValue;
26
+ }
27
+ return result;
28
+ }
29
+ /**
30
+ * Generate Zod schema string for a property type.
31
+ */
32
+ function getZodSchemaForType(propDef, _fieldName, options) {
33
+ const isNullable = propDef.nullable ?? false;
34
+ let schema = '';
35
+ // Check for simple custom types
36
+ const simpleType = options.customTypes.simple[propDef.type];
37
+ if (simpleType) {
38
+ schema = 'z.string()';
39
+ if (simpleType.length) {
40
+ schema += `.max(${simpleType.length})`;
41
+ }
42
+ if (isNullable) {
43
+ schema += '.optional().nullable()';
44
+ }
45
+ return schema;
46
+ }
47
+ switch (propDef.type) {
48
+ case 'String':
49
+ case 'Text':
50
+ case 'MediumText':
51
+ case 'LongText':
52
+ case 'Password':
53
+ schema = 'z.string()';
54
+ if (!isNullable)
55
+ schema += '.min(1)';
56
+ if (propDef.maxLength || propDef.length) {
57
+ schema += `.max(${propDef.maxLength ?? propDef.length})`;
58
+ }
59
+ if (propDef.minLength && propDef.minLength > 1) {
60
+ schema = schema.replace('.min(1)', `.min(${propDef.minLength})`);
61
+ }
62
+ break;
63
+ case 'Email':
64
+ schema = 'z.string().email()';
65
+ if (propDef.maxLength || propDef.length) {
66
+ schema += `.max(${propDef.maxLength ?? propDef.length ?? 255})`;
67
+ }
68
+ break;
69
+ case 'TinyInt':
70
+ case 'Int':
71
+ case 'BigInt':
72
+ schema = 'z.number().int()';
73
+ if (propDef.min !== undefined)
74
+ schema += `.gte(${propDef.min})`;
75
+ if (propDef.max !== undefined)
76
+ schema += `.lte(${propDef.max})`;
77
+ break;
78
+ case 'Float':
79
+ case 'Decimal':
80
+ schema = 'z.number()';
81
+ if (propDef.min !== undefined)
82
+ schema += `.gte(${propDef.min})`;
83
+ if (propDef.max !== undefined)
84
+ schema += `.lte(${propDef.max})`;
85
+ break;
86
+ case 'Boolean':
87
+ schema = 'z.boolean()';
88
+ break;
89
+ case 'Date':
90
+ schema = 'z.string().date()';
91
+ break;
92
+ case 'DateTime':
93
+ case 'Timestamp':
94
+ schema = 'z.string().datetime({ offset: true })';
95
+ break;
96
+ case 'Time':
97
+ schema = 'z.string().time()';
98
+ break;
99
+ case 'Json':
100
+ schema = 'z.unknown()';
101
+ break;
102
+ case 'EnumRef':
103
+ if (typeof propDef.enum === 'string') {
104
+ schema = `z.nativeEnum(${propDef.enum})`;
105
+ }
106
+ else {
107
+ schema = 'z.string()';
108
+ }
109
+ break;
110
+ case 'Enum':
111
+ if (typeof propDef.enum === 'string') {
112
+ schema = `z.nativeEnum(${propDef.enum})`;
113
+ }
114
+ else if (Array.isArray(propDef.enum)) {
115
+ const values = propDef.enum.map(v => `'${v}'`).join(', ');
116
+ schema = `z.enum([${values}])`;
117
+ }
118
+ else {
119
+ schema = 'z.string()';
120
+ }
121
+ break;
122
+ case 'Lookup':
123
+ schema = 'z.number().int().positive()';
124
+ break;
125
+ case 'Association':
126
+ case 'File':
127
+ return '';
128
+ default:
129
+ schema = 'z.string()';
130
+ }
131
+ if (isNullable && schema) {
132
+ schema += '.optional().nullable()';
133
+ }
134
+ if (propDef.pattern && schema) {
135
+ schema += `.regex(/${propDef.pattern}/)`;
136
+ }
137
+ return schema;
138
+ }
139
+ /**
140
+ * Generate Zod schemas for compound type expanded columns.
141
+ */
142
+ function generateCompoundTypeSchemas(propName, schema, options) {
143
+ const expanded = schema.expandedProperties?.[propName];
144
+ if (!expanded)
145
+ return [];
146
+ const schemas = [];
147
+ const compoundDef = options.customTypes.compound[expanded.sourceType];
148
+ const propDef = schema.properties?.[propName];
149
+ const propFields = propDef?.fields;
150
+ for (const col of expanded.columns) {
151
+ const fieldOverride = propFields?.[col.suffix];
152
+ const compoundField = compoundDef?.fields.find(f => f.suffix === col.suffix);
153
+ const isNullable = col.nullable ?? compoundField?.nullable ?? false;
154
+ let zodSchema;
155
+ if (col.type === 'Enum' && compoundField?.enumRef) {
156
+ // Enum reference (e.g., Prefecture)
157
+ zodSchema = `z.nativeEnum(${compoundField.enumRef})`;
158
+ if (isNullable)
159
+ zodSchema += '.optional().nullable()';
160
+ }
161
+ else if (col.type === 'Enum' && col.enum) {
162
+ // Inline enum
163
+ const values = col.enum.map(v => `'${v}'`).join(', ');
164
+ zodSchema = `z.enum([${values}])`;
165
+ if (isNullable)
166
+ zodSchema += '.optional().nullable()';
167
+ }
168
+ else {
169
+ // String type with length
170
+ zodSchema = 'z.string()';
171
+ if (!isNullable)
172
+ zodSchema += '.min(1)';
173
+ const length = fieldOverride?.length ?? col.length;
174
+ if (length)
175
+ zodSchema += `.max(${length})`;
176
+ if (isNullable)
177
+ zodSchema += '.optional().nullable()';
178
+ }
179
+ schemas.push({
180
+ fieldName: col.name,
181
+ schema: zodSchema,
182
+ inCreate: true,
183
+ inUpdate: true,
184
+ });
185
+ }
186
+ return schemas;
187
+ }
188
+ /**
189
+ * Generate Zod schemas for all properties in a schema.
190
+ */
191
+ export function generateZodSchemas(schema, options) {
192
+ const schemas = [];
193
+ if (!schema.properties)
194
+ return schemas;
195
+ const propNames = schema.propertyOrder ?? Object.keys(schema.properties);
196
+ for (const propName of propNames) {
197
+ const propDef = schema.properties[propName];
198
+ if (!propDef)
199
+ continue;
200
+ // Check for compound type expansion
201
+ if (schema.expandedProperties?.[propName]) {
202
+ schemas.push(...generateCompoundTypeSchemas(propName, schema, options));
203
+ continue;
204
+ }
205
+ const zodSchema = getZodSchemaForType(propDef, propName, options);
206
+ if (!zodSchema)
207
+ continue;
208
+ schemas.push({
209
+ fieldName: toSnakeCase(propName),
210
+ schema: zodSchema,
211
+ inCreate: true,
212
+ inUpdate: true,
213
+ });
214
+ }
215
+ return schemas;
216
+ }
217
+ /**
218
+ * Generate display names and placeholders for a schema.
219
+ */
220
+ export function generateDisplayNames(schema, options) {
221
+ const { locales, fallbackLocale } = options;
222
+ const displayName = getMultiLocaleDisplayName(schema.displayName, locales, fallbackLocale, schema.name);
223
+ const propertyDisplayNames = {};
224
+ const propertyPlaceholders = {};
225
+ if (schema.properties) {
226
+ const propNames = schema.propertyOrder ?? Object.keys(schema.properties);
227
+ for (const propName of propNames) {
228
+ const propDef = schema.properties[propName];
229
+ if (!propDef)
230
+ continue;
231
+ const fieldName = toSnakeCase(propName);
232
+ // Compound type: expand to individual fields
233
+ if (schema.expandedProperties?.[propName]) {
234
+ const expanded = schema.expandedProperties[propName];
235
+ // Add compound-level displayName
236
+ if (propDef.displayName) {
237
+ propertyDisplayNames[fieldName] = getMultiLocaleDisplayName(propDef.displayName, locales, fallbackLocale, propName);
238
+ }
239
+ for (const col of expanded.columns) {
240
+ const fieldOverride = propDef.fields?.[col.suffix];
241
+ // Display name for expanded field
242
+ const labelSource = fieldOverride?.displayName;
243
+ if (labelSource) {
244
+ propertyDisplayNames[col.name] = getMultiLocaleDisplayName(labelSource, locales, fallbackLocale, col.suffix);
245
+ }
246
+ else {
247
+ // Fallback: parent displayName + suffix
248
+ const parentLabel = getMultiLocaleDisplayName(propDef.displayName, locales, fallbackLocale, propName);
249
+ const suffixed = {};
250
+ for (const locale of locales) {
251
+ suffixed[locale] = `${parentLabel[locale]} (${col.suffix})`;
252
+ }
253
+ propertyDisplayNames[col.name] = suffixed;
254
+ }
255
+ // Placeholder for expanded field
256
+ if (fieldOverride?.placeholder) {
257
+ propertyPlaceholders[col.name] = getMultiLocaleDisplayName(fieldOverride.placeholder, locales, fallbackLocale, '');
258
+ }
259
+ }
260
+ continue;
261
+ }
262
+ // Regular field display name
263
+ propertyDisplayNames[fieldName] = getMultiLocaleDisplayName(propDef.displayName, locales, fallbackLocale, propName);
264
+ // Placeholder
265
+ if (propDef.placeholder) {
266
+ propertyPlaceholders[fieldName] = getMultiLocaleDisplayName(propDef.placeholder, locales, fallbackLocale, '');
267
+ }
268
+ }
269
+ }
270
+ return { displayName, propertyDisplayNames, propertyPlaceholders };
271
+ }
272
+ /**
273
+ * Get fields to exclude from create/update schemas.
274
+ */
275
+ export function getExcludedFields(schema) {
276
+ const createExclude = new Set();
277
+ const updateExclude = new Set();
278
+ if (schema.options?.id !== false) {
279
+ createExclude.add('id');
280
+ updateExclude.add('id');
281
+ }
282
+ if (schema.options?.timestamps !== false) {
283
+ for (const f of ['created_at', 'updated_at']) {
284
+ createExclude.add(f);
285
+ updateExclude.add(f);
286
+ }
287
+ }
288
+ if (schema.options?.softDelete) {
289
+ createExclude.add('deleted_at');
290
+ updateExclude.add('deleted_at');
291
+ }
292
+ return { create: createExclude, update: updateExclude };
293
+ }
294
+ /**
295
+ * Format Zod schemas section for a base file.
296
+ */
297
+ export function formatZodSchemasSection(schemaName, zodSchemas, displayNames, excludedFields) {
298
+ const parts = [];
299
+ const lowerName = schemaName.charAt(0).toLowerCase() + schemaName.slice(1);
300
+ // I18n section
301
+ parts.push(`// ============================================================================\n`);
302
+ parts.push(`// I18n (Internationalization)\n`);
303
+ parts.push(`// ============================================================================\n\n`);
304
+ parts.push(`/**\n * Unified i18n object for ${schemaName}\n */\n`);
305
+ parts.push(`export const ${lowerName}I18n = {\n`);
306
+ parts.push(` label: ${JSON.stringify(displayNames.displayName)},\n`);
307
+ parts.push(` fields: {\n`);
308
+ for (const [propName, labelMap] of Object.entries(displayNames.propertyDisplayNames)) {
309
+ const placeholderMap = displayNames.propertyPlaceholders[propName];
310
+ parts.push(` ${propName}: {\n`);
311
+ parts.push(` label: ${JSON.stringify(labelMap)},\n`);
312
+ if (placeholderMap) {
313
+ parts.push(` placeholder: ${JSON.stringify(placeholderMap)},\n`);
314
+ }
315
+ parts.push(` },\n`);
316
+ }
317
+ parts.push(` },\n`);
318
+ parts.push(`} as const;\n\n`);
319
+ // Zod Schemas section
320
+ parts.push(`// ============================================================================\n`);
321
+ parts.push(`// Zod Schemas\n`);
322
+ parts.push(`// ============================================================================\n\n`);
323
+ parts.push(`/** Field schemas for ${schemaName} */\n`);
324
+ parts.push(`export const base${schemaName}Schemas = {\n`);
325
+ for (const prop of zodSchemas) {
326
+ parts.push(` ${prop.fieldName}: ${prop.schema},\n`);
327
+ }
328
+ parts.push(`} as const;\n\n`);
329
+ // Create Schema
330
+ const createFields = zodSchemas.filter(p => p.inCreate && !excludedFields.create.has(p.fieldName));
331
+ parts.push(`/** Create schema for ${schemaName} */\n`);
332
+ parts.push(`export const base${schemaName}CreateSchema = z.object({\n`);
333
+ for (const prop of createFields) {
334
+ parts.push(` ${prop.fieldName}: base${schemaName}Schemas.${prop.fieldName},\n`);
335
+ }
336
+ parts.push(`});\n\n`);
337
+ // Update Schema
338
+ parts.push(`/** Update schema for ${schemaName} */\n`);
339
+ parts.push(`export const base${schemaName}UpdateSchema = base${schemaName}CreateSchema.partial();\n\n`);
340
+ // Inferred Types
341
+ parts.push(`// ============================================================================\n`);
342
+ parts.push(`// Inferred Types\n`);
343
+ parts.push(`// ============================================================================\n\n`);
344
+ parts.push(`export type Base${schemaName}Create = z.infer<typeof base${schemaName}CreateSchema>;\n`);
345
+ parts.push(`export type Base${schemaName}Update = z.infer<typeof base${schemaName}UpdateSchema>;\n\n`);
346
+ // Helper Functions
347
+ parts.push(`// ============================================================================\n`);
348
+ parts.push(`// I18n Helper Functions\n`);
349
+ parts.push(`// ============================================================================\n\n`);
350
+ parts.push(`/** Get model label for a specific locale */\n`);
351
+ parts.push(`export function get${schemaName}Label(locale: string): string {\n`);
352
+ parts.push(` return ${lowerName}I18n.label[locale as keyof typeof ${lowerName}I18n.label] ?? ${lowerName}I18n.label['en'] ?? '${schemaName}';\n`);
353
+ parts.push(`}\n\n`);
354
+ parts.push(`/** Get field label for a specific locale */\n`);
355
+ parts.push(`export function get${schemaName}FieldLabel(field: string, locale: string): string {\n`);
356
+ parts.push(` const fieldI18n = ${lowerName}I18n.fields[field as keyof typeof ${lowerName}I18n.fields];\n`);
357
+ parts.push(` if (!fieldI18n) return field;\n`);
358
+ parts.push(` return fieldI18n.label[locale as keyof typeof fieldI18n.label] ?? fieldI18n.label['en'] ?? field;\n`);
359
+ parts.push(`}\n\n`);
360
+ parts.push(`/** Get field placeholder for a specific locale */\n`);
361
+ parts.push(`export function get${schemaName}FieldPlaceholder(field: string, locale: string): string {\n`);
362
+ parts.push(` const fieldI18n = ${lowerName}I18n.fields[field as keyof typeof ${lowerName}I18n.fields];\n`);
363
+ parts.push(` if (!fieldI18n || !('placeholder' in fieldI18n)) return '';\n`);
364
+ parts.push(` const placeholder = fieldI18n.placeholder as Record<string, string>;\n`);
365
+ parts.push(` return placeholder[locale] ?? placeholder['en'] ?? '';\n`);
366
+ parts.push(`}\n`);
367
+ return parts.join('');
368
+ }
369
+ /**
370
+ * Format user model file with Zod re-exports.
371
+ */
372
+ export function formatZodModelFile(schemaName) {
373
+ const lowerName = schemaName.charAt(0).toLowerCase() + schemaName.slice(1);
374
+ return `/**
375
+ * ${schemaName} Model
376
+ *
377
+ * This file extends the auto-generated base interface.
378
+ * You can add custom methods, computed properties, or override types/schemas here.
379
+ * This file will NOT be overwritten by the generator.
380
+ */
381
+
382
+ import { z } from 'zod';
383
+ import type { ${schemaName} as ${schemaName}Base } from './base/${schemaName}.js';
384
+ import {
385
+ base${schemaName}Schemas,
386
+ base${schemaName}CreateSchema,
387
+ base${schemaName}UpdateSchema,
388
+ ${lowerName}I18n,
389
+ get${schemaName}Label,
390
+ get${schemaName}FieldLabel,
391
+ get${schemaName}FieldPlaceholder,
392
+ } from './base/${schemaName}.js';
393
+
394
+ // ============================================================================
395
+ // Types (extend or re-export)
396
+ // ============================================================================
397
+
398
+ export interface ${schemaName} extends ${schemaName}Base {
399
+ // Add custom properties here
400
+ }
401
+
402
+ // ============================================================================
403
+ // Schemas (extend or re-export)
404
+ // ============================================================================
405
+
406
+ export const ${lowerName}Schemas = { ...base${schemaName}Schemas };
407
+ export const ${lowerName}CreateSchema = base${schemaName}CreateSchema;
408
+ export const ${lowerName}UpdateSchema = base${schemaName}UpdateSchema;
409
+
410
+ // ============================================================================
411
+ // Types
412
+ // ============================================================================
413
+
414
+ export type ${schemaName}Create = z.infer<typeof ${lowerName}CreateSchema>;
415
+ export type ${schemaName}Update = z.infer<typeof ${lowerName}UpdateSchema>;
416
+
417
+ // Re-export i18n and helpers
418
+ export {
419
+ ${lowerName}I18n,
420
+ get${schemaName}Label,
421
+ get${schemaName}FieldLabel,
422
+ get${schemaName}FieldPlaceholder,
423
+ };
424
+
425
+ // Re-export base type for internal use
426
+ export type { ${schemaName}Base };
427
+ `;
428
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@omnifyjp/ts",
3
+ "version": "0.3.0",
4
+ "description": "TypeScript model type generator from Omnify schemas.json",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "omnify-ts": "dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "generate-l12": "node dist/cli.js --config ../../examples/l12/omnify.yaml",
15
+ "test": "vitest run"
16
+ },
17
+ "dependencies": {
18
+ "commander": "^13.0.0",
19
+ "yaml": "^2.7.0",
20
+ "zod": "^3.24.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.0.0",
24
+ "typescript": "^5.7.0",
25
+ "vitest": "^4.0.18"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "license": "MIT"
31
+ }