@omnifyjp/omnify 0.1.1 → 0.1.3

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.
@@ -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,196 @@
1
+ /**
2
+ * @omnify/ts — Internal types
3
+ *
4
+ * Types for schemas.json input and TypeScript code generation output.
5
+ */
6
+ /** Localized string — either a plain string or a locale map. */
7
+ export type LocalizedString = string | Record<string, string>;
8
+ /** Top-level schemas.json structure. */
9
+ export interface SchemasJson {
10
+ readonly generatedAt: string;
11
+ readonly database: {
12
+ readonly driver: string;
13
+ };
14
+ readonly connections: Record<string, string>;
15
+ readonly locale: {
16
+ readonly locales: string[];
17
+ readonly defaultLocale: string;
18
+ readonly fallbackLocale: string;
19
+ };
20
+ readonly customTypes: {
21
+ readonly compound: Record<string, CompoundTypeDefinition>;
22
+ readonly simple: Record<string, SimpleTypeDefinition>;
23
+ readonly enums: Record<string, string[]>;
24
+ };
25
+ readonly schemas: Record<string, SchemaDefinition>;
26
+ }
27
+ /** Compound type definition (e.g., JapaneseName). */
28
+ export interface CompoundTypeDefinition {
29
+ readonly fields: readonly CompoundField[];
30
+ }
31
+ /** A field within a compound type. */
32
+ export interface CompoundField {
33
+ readonly suffix: string;
34
+ readonly mapsTo?: string;
35
+ readonly length?: number;
36
+ readonly nullable?: boolean;
37
+ readonly enumRef?: string;
38
+ }
39
+ /** Simple custom type (e.g., JapanesePhone). */
40
+ export interface SimpleTypeDefinition {
41
+ readonly mapsTo: string;
42
+ readonly length?: number;
43
+ }
44
+ /** Expanded property info (pre-computed by omnify-go). */
45
+ export interface ExpandedProperty {
46
+ readonly sourceType: string;
47
+ readonly columns: readonly ExpandedColumn[];
48
+ }
49
+ /** A single expanded column from a compound type. */
50
+ export interface ExpandedColumn {
51
+ readonly name: string;
52
+ readonly suffix: string;
53
+ readonly type: string;
54
+ readonly length?: number;
55
+ readonly nullable?: boolean;
56
+ readonly enum?: readonly string[];
57
+ }
58
+ /** Schema options. */
59
+ export interface SchemaOptions {
60
+ readonly id?: boolean;
61
+ readonly idType?: string;
62
+ readonly timestamps?: boolean;
63
+ readonly softDelete?: boolean;
64
+ readonly hidden?: boolean;
65
+ readonly tableName?: string;
66
+ readonly indexes?: readonly unknown[];
67
+ readonly unique?: readonly unknown[];
68
+ }
69
+ /** Schema definition from schemas.json. */
70
+ export interface SchemaDefinition {
71
+ readonly name: string;
72
+ readonly tableName?: string;
73
+ readonly connection?: string;
74
+ readonly displayName?: LocalizedString;
75
+ readonly group?: string;
76
+ readonly kind?: 'object' | 'enum' | 'pivot' | 'partial' | 'extend';
77
+ readonly options?: SchemaOptions;
78
+ readonly properties?: Record<string, PropertyDefinition>;
79
+ readonly propertyOrder?: readonly string[];
80
+ readonly expandedProperties?: Record<string, ExpandedProperty>;
81
+ readonly values?: readonly EnumValueDefinition[];
82
+ readonly pivotFor?: readonly string[];
83
+ }
84
+ /** Property definition within a schema. */
85
+ export interface PropertyDefinition {
86
+ readonly type: string;
87
+ readonly displayName?: LocalizedString;
88
+ readonly description?: LocalizedString;
89
+ readonly placeholder?: LocalizedString;
90
+ readonly nullable?: boolean;
91
+ readonly unique?: boolean;
92
+ readonly primary?: boolean;
93
+ readonly default?: unknown;
94
+ readonly length?: number;
95
+ readonly minLength?: number;
96
+ readonly maxLength?: number;
97
+ readonly min?: number;
98
+ readonly max?: number;
99
+ readonly unsigned?: boolean;
100
+ readonly precision?: number;
101
+ readonly scale?: number;
102
+ readonly pattern?: string;
103
+ readonly enum?: string | readonly string[];
104
+ readonly relation?: string;
105
+ readonly target?: string;
106
+ readonly targets?: readonly string[];
107
+ readonly onDelete?: string;
108
+ readonly morphName?: string;
109
+ readonly joinTable?: string;
110
+ readonly mappedBy?: string;
111
+ readonly useCurrent?: boolean;
112
+ readonly fields?: Record<string, FieldOverride>;
113
+ }
114
+ /** Field override for compound type properties. */
115
+ export interface FieldOverride {
116
+ readonly nullable?: boolean;
117
+ readonly length?: number;
118
+ readonly hidden?: boolean;
119
+ readonly displayName?: LocalizedString;
120
+ readonly placeholder?: LocalizedString;
121
+ }
122
+ /** Enum value definition (for schema enums). */
123
+ export interface EnumValueDefinition {
124
+ readonly value: string;
125
+ readonly label?: LocalizedString;
126
+ readonly extra?: Record<string, unknown>;
127
+ }
128
+ /** File category for organizing output. */
129
+ export type FileCategory = 'schema' | 'base' | 'enum' | 'plugin-enum';
130
+ /** Generated TypeScript file. */
131
+ export interface TypeScriptFile {
132
+ readonly filePath: string;
133
+ readonly content: string;
134
+ readonly types: readonly string[];
135
+ readonly overwrite: boolean;
136
+ readonly category?: FileCategory;
137
+ }
138
+ /** TypeScript property definition. */
139
+ export interface TSProperty {
140
+ readonly name: string;
141
+ readonly type: string;
142
+ readonly optional: boolean;
143
+ readonly readonly: boolean;
144
+ readonly comment?: string;
145
+ }
146
+ /** TypeScript interface definition. */
147
+ export interface TSInterface {
148
+ readonly name: string;
149
+ readonly properties: readonly TSProperty[];
150
+ readonly extends?: readonly string[];
151
+ readonly comment?: string;
152
+ readonly dependencies?: readonly string[];
153
+ readonly enumDependencies?: readonly string[];
154
+ }
155
+ /** TypeScript enum definition. */
156
+ export interface TSEnum {
157
+ readonly name: string;
158
+ readonly values: readonly TSEnumValue[];
159
+ readonly comment?: string;
160
+ }
161
+ /** Multi-locale string map. */
162
+ export type LocaleMap = Record<string, string>;
163
+ /** TypeScript enum value. */
164
+ export interface TSEnumValue {
165
+ readonly name: string;
166
+ readonly value: string | number;
167
+ readonly label?: string | LocaleMap;
168
+ readonly extra?: Record<string, unknown>;
169
+ }
170
+ /** TypeScript type alias definition. */
171
+ export interface TSTypeAlias {
172
+ readonly name: string;
173
+ readonly type: string;
174
+ readonly comment?: string;
175
+ }
176
+ /** Zod schema information for a property. */
177
+ export interface ZodPropertySchema {
178
+ readonly fieldName: string;
179
+ readonly schema: string;
180
+ readonly inCreate: boolean;
181
+ readonly inUpdate: boolean;
182
+ readonly comment?: string;
183
+ }
184
+ /** Display names for a schema. */
185
+ export interface SchemaDisplayNames {
186
+ readonly displayName: LocaleMap;
187
+ readonly propertyDisplayNames: Record<string, LocaleMap>;
188
+ readonly propertyPlaceholders: Record<string, LocaleMap>;
189
+ }
190
+ /** Generation options (internal). */
191
+ export interface GeneratorOptions {
192
+ readonly locales: string[];
193
+ readonly defaultLocale: string;
194
+ readonly fallbackLocale: string;
195
+ readonly customTypes: SchemasJson['customTypes'];
196
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @omnify/ts — Internal types
3
+ *
4
+ * Types for schemas.json input and TypeScript code generation output.
5
+ */
6
+ export {};
@@ -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;