@an5/orm 1.0.8 → 1.1.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/README.md +174 -175
- package/dist/an5Metadata.js +64 -0
- package/dist/an5Orm.d.ts +92 -3
- package/dist/an5Orm.js +697 -206
- package/dist/config.d.ts +9 -1
- package/dist/config.js +13 -18
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/generator/src/api.d.ts +35 -0
- package/dist/generator/src/api.js +58 -0
- package/dist/generator/src/code-generator.d.ts +14 -13
- package/dist/generator/src/code-generator.js +249 -225
- package/dist/generator/src/config.d.ts +162 -0
- package/dist/generator/src/config.js +354 -0
- package/dist/generator/src/dialect.d.ts +88 -0
- package/dist/generator/src/dialect.js +318 -0
- package/dist/generator/src/dotnet-generator.d.ts +21 -11
- package/dist/generator/src/dotnet-generator.js +1053 -546
- package/dist/generator/src/field-types.d.ts +96 -0
- package/dist/generator/src/field-types.js +433 -0
- package/dist/generator/src/golang-generator.d.ts +36 -0
- package/dist/generator/src/golang-generator.js +1229 -0
- package/dist/generator/src/index.d.ts +1 -1
- package/dist/generator/src/index.js +137 -88
- package/dist/generator/src/metadata-generator.d.ts +11 -8
- package/dist/generator/src/metadata-generator.js +105 -90
- package/dist/generator/src/parser.d.ts +37 -11
- package/dist/generator/src/parser.js +208 -206
- package/dist/generator/src/push-apply.d.ts +37 -0
- package/dist/generator/src/push-apply.js +109 -0
- package/dist/generator/src/push-schema.d.ts +93 -0
- package/dist/generator/src/push-schema.js +189 -0
- package/dist/generator/src/python-generator.d.ts +37 -9
- package/dist/generator/src/python-generator.js +428 -105
- package/dist/generator/src/rust-generator.d.ts +38 -0
- package/dist/generator/src/rust-generator.js +971 -0
- package/dist/generator/src/suggest.d.ts +17 -0
- package/dist/generator/src/suggest.js +45 -0
- package/dist/generator/src/type-kinds.d.ts +27 -0
- package/dist/generator/src/type-kinds.js +72 -0
- package/dist/generator/src/types.d.ts +60 -27
- package/dist/generator/src/types.js +41 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +19 -8
- package/dist/logger.js +1 -1
- package/dist/migration-core.d.ts +102 -0
- package/dist/migration-core.js +731 -0
- package/dist/orm.d.ts +147 -0
- package/dist/orm.js +1654 -0
- package/dist/provider-support.d.ts +27 -0
- package/dist/provider-support.js +50 -0
- package/dist/scripts/release-cli.d.ts +2 -2
- package/dist/scripts/release-cli.js +115 -115
- package/dist/sql-utils.d.ts +3 -1
- package/dist/sql-utils.js +94 -24
- package/generator/src/api.ts +45 -0
- package/generator/src/code-generator.ts +259 -230
- package/generator/src/config.ts +496 -0
- package/generator/src/dialect.ts +415 -0
- package/generator/src/dotnet-generator.ts +1071 -544
- package/generator/src/field-types.ts +473 -0
- package/generator/src/golang-generator.ts +1267 -0
- package/generator/src/index.ts +146 -116
- package/generator/src/metadata-generator.ts +116 -101
- package/generator/src/parser.ts +234 -202
- package/generator/src/push-apply.ts +148 -0
- package/generator/src/push-schema.ts +250 -0
- package/generator/src/python-generator.ts +480 -114
- package/generator/src/rust-generator.ts +974 -0
- package/generator/src/suggest.ts +41 -0
- package/generator/src/type-kinds.ts +78 -0
- package/generator/src/types.ts +77 -29
- package/package.json +32 -6
package/generator/src/index.ts
CHANGED
|
@@ -1,116 +1,146 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { loadConfig, providerFromConfig, ConfigError, formatIssues } from './config';
|
|
3
|
+
import { FieldTypeError, PROVIDER_LABELS } from './field-types';
|
|
4
|
+
import { SchemaParser } from './parser';
|
|
5
|
+
import { CodeGenerator } from './code-generator';
|
|
6
|
+
import { MetadataGenerator } from './metadata-generator';
|
|
7
|
+
import { PythonGenerator } from './python-generator';
|
|
8
|
+
import { DotnetGenerator } from './dotnet-generator';
|
|
9
|
+
import { GolangGenerator } from './golang-generator';
|
|
10
|
+
import { RustGenerator } from './rust-generator';
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
|
|
14
|
+
function clearGeneratedFiles(outputDir: string, extension: string) {
|
|
15
|
+
if (!fs.existsSync(outputDir)) {
|
|
16
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const entry of fs.readdirSync(outputDir)) {
|
|
21
|
+
const fullPath = path.join(outputDir, entry);
|
|
22
|
+
const stat = fs.statSync(fullPath);
|
|
23
|
+
if (stat.isDirectory()) continue;
|
|
24
|
+
if (entry.endsWith(extension)) {
|
|
25
|
+
fs.unlinkSync(fullPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function clearGeneratedPythonFiles(outputDir: string) {
|
|
31
|
+
if (!fs.existsSync(outputDir)) {
|
|
32
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Delete generated .py files but preserve hand-maintained adapter.
|
|
37
|
+
const preserve = new Set(['an5_adapter.py']);
|
|
38
|
+
for (const entry of fs.readdirSync(outputDir)) {
|
|
39
|
+
const fullPath = path.join(outputDir, entry);
|
|
40
|
+
const stat = fs.statSync(fullPath);
|
|
41
|
+
if (stat.isDirectory()) continue;
|
|
42
|
+
if (entry.endsWith('.py') && !preserve.has(entry)) {
|
|
43
|
+
fs.unlinkSync(fullPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
// Loaded through the validating loader, so a mistyped key or a wrong type
|
|
50
|
+
// stops here instead of quietly generating somewhere else.
|
|
51
|
+
let config: ReturnType<typeof loadConfig>;
|
|
52
|
+
try {
|
|
53
|
+
config = loadConfig();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof ConfigError) {
|
|
56
|
+
console.error(`❌ ${err.message}:`);
|
|
57
|
+
console.error(formatIssues(err.issues));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
console.warn('⚠️ Could not load an5Orm.config.js/.cjs, using defaults.', err);
|
|
61
|
+
config = loadConfig(path.join(process.cwd(), 'no-such-dir'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { rootDir } = config;
|
|
65
|
+
const schemaDir = config.outputs.schemaDir;
|
|
66
|
+
const outputTypesDir = config.outputs.typescriptDir;
|
|
67
|
+
const outputMetadataPath = config.outputs.typescriptMetadataFile;
|
|
68
|
+
const outputPythonMetadataPath = config.outputs.pythonMetadataFile;
|
|
69
|
+
const outputDotnetDir = config.outputs.dotnetDir;
|
|
70
|
+
const outputGolangDir = config.outputs.golangDir;
|
|
71
|
+
const outputRustDir = config.outputs.rustDir;
|
|
72
|
+
const generateMetadata = config.config.generation.generateMetadata;
|
|
73
|
+
|
|
74
|
+
console.log('🚀 Starting ORM generation...');
|
|
75
|
+
|
|
76
|
+
// The target provider comes from the connection string: both the allowed
|
|
77
|
+
// field types and the SQL follow it, instead of one shared list.
|
|
78
|
+
const provider = providerFromConfig(config.config, process.env);
|
|
79
|
+
console.log(`🗄️ Provider: ${PROVIDER_LABELS[provider]} (${provider})`);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
clearGeneratedFiles(outputTypesDir, '.ts');
|
|
83
|
+
clearGeneratedFiles(outputDotnetDir, '.cs');
|
|
84
|
+
clearGeneratedFiles(outputGolangDir, '.go');
|
|
85
|
+
clearGeneratedFiles(path.join(outputRustDir, 'src'), '.rs');
|
|
86
|
+
const pythonDirEarly = path.dirname(outputPythonMetadataPath);
|
|
87
|
+
clearGeneratedPythonFiles(pythonDirEarly);
|
|
88
|
+
if (config.config.generation.generateMetadata && fs.existsSync(outputMetadataPath)) {
|
|
89
|
+
fs.unlinkSync(outputMetadataPath);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const parser = new SchemaParser(schemaDir, provider);
|
|
93
|
+
const models = await parser.parse();
|
|
94
|
+
console.log(`📦 Parsed ${models.length} models from schema.`);
|
|
95
|
+
|
|
96
|
+
const codeGen = new CodeGenerator(outputTypesDir);
|
|
97
|
+
codeGen.generate(models);
|
|
98
|
+
console.log(`✨ Generated modular types in ${outputTypesDir}`);
|
|
99
|
+
|
|
100
|
+
if (generateMetadata) {
|
|
101
|
+
const metadataDir = path.dirname(outputMetadataPath);
|
|
102
|
+
if (!fs.existsSync(metadataDir)) {
|
|
103
|
+
fs.mkdirSync(metadataDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
const metadataGen = new MetadataGenerator(outputMetadataPath);
|
|
106
|
+
metadataGen.generate(models);
|
|
107
|
+
console.log(`✨ Generated metadata in ${outputMetadataPath}`);
|
|
108
|
+
} else {
|
|
109
|
+
console.log('⏭ Skipping metadata (generation.generateMetadata is false)');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const pythonDir = path.dirname(outputPythonMetadataPath);
|
|
113
|
+
if (!fs.existsSync(pythonDir)) {
|
|
114
|
+
fs.mkdirSync(pythonDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
const pythonGen = new PythonGenerator(outputPythonMetadataPath);
|
|
117
|
+
pythonGen.generate(models);
|
|
118
|
+
console.log(`✨ Generated Python metadata and client models in ${pythonDir}`);
|
|
119
|
+
|
|
120
|
+
const dotnetGen = new DotnetGenerator(outputDotnetDir);
|
|
121
|
+
dotnetGen.generate(models);
|
|
122
|
+
console.log(`✨ Generated .NET models in ${outputDotnetDir}`);
|
|
123
|
+
|
|
124
|
+
const golangGen = new GolangGenerator(outputGolangDir);
|
|
125
|
+
golangGen.generate(models);
|
|
126
|
+
console.log(`✨ Generated Golang models in ${outputGolangDir}`);
|
|
127
|
+
|
|
128
|
+
const rustGen = new RustGenerator(outputRustDir);
|
|
129
|
+
rustGen.generate(models);
|
|
130
|
+
console.log(`✨ Generated Rust client in ${outputRustDir}`);
|
|
131
|
+
|
|
132
|
+
console.log('✅ ORM generation completed successfully.');
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// Print every bad field type with its provider, instead of the parser's
|
|
135
|
+
// nested error object.
|
|
136
|
+
if (error instanceof FieldTypeError) {
|
|
137
|
+
console.error(`❌ ${error.message}:`);
|
|
138
|
+
console.error(formatIssues(error.issues));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
console.error('❌ ORM generation failed:', error);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main();
|
|
@@ -1,101 +1,116 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Model } from './types';
|
|
4
|
-
|
|
5
|
-
export class MetadataGenerator {
|
|
6
|
-
constructor(
|
|
7
|
-
private outputPath: string,
|
|
8
|
-
private relationImport: string = 'import type { RelationDef } from "@an5/orm";'
|
|
9
|
-
) {}
|
|
10
|
-
|
|
11
|
-
public generate(models: Model[]) {
|
|
12
|
-
let metaContent = '// This file is auto-generated. Do not edit directly.\n\n';
|
|
13
|
-
metaContent += `${this.relationImport}\n\n`;
|
|
14
|
-
|
|
15
|
-
metaContent += 'export const modelToTable: Record<string, string> = {\n';
|
|
16
|
-
for (const model of models) {
|
|
17
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
18
|
-
const fullTableName =
|
|
19
|
-
for (const prop of props) {
|
|
20
|
-
metaContent += ` ${prop}: "${fullTableName}",\n`;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
metaContent += '};\n\n';
|
|
24
|
-
|
|
25
|
-
metaContent += 'export const modelDescriptions: Record<string, string | undefined> = {\n';
|
|
26
|
-
for (const model of models) {
|
|
27
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
28
|
-
for (const prop of props) {
|
|
29
|
-
metaContent += ` ${prop}: ${model.description ? JSON.stringify(model.description) : 'undefined'},\n`;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
metaContent += '};\n\n';
|
|
33
|
-
|
|
34
|
-
metaContent += 'export const modelFields: Record<string, Record<string, { ts: string; sql: string; description?: string }>> = {\n';
|
|
35
|
-
for (const model of models) {
|
|
36
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
37
|
-
const fieldsStr = `{ ${model.fields.map(f => `${f.name}: ${this.formatFieldMetadata(f)}`).join(', ')} }`;
|
|
38
|
-
for (const prop of props) {
|
|
39
|
-
metaContent += ` ${prop}: ${fieldsStr},\n`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
metaContent += '};\n\n';
|
|
43
|
-
|
|
44
|
-
metaContent += 'export const relationMap: Record<string, Record<string, RelationDef>> = {\n';
|
|
45
|
-
for (const model of models) {
|
|
46
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
47
|
-
let relationsContent = ` {\n`;
|
|
48
|
-
for (const rel of model.relations) {
|
|
49
|
-
relationsContent += ` ${rel.name}: { modelName: "${this.toCamelCase(rel.type)}", relationType: "${rel.isArray ? 'many' : 'one'}", foreignKey: "${rel.foreignKey || 'id'}", localKey: "${rel.localKey || 'id'}" },\n`;
|
|
50
|
-
}
|
|
51
|
-
relationsContent += ` }`;
|
|
52
|
-
|
|
53
|
-
for (const prop of props) {
|
|
54
|
-
metaContent += ` ${prop}: ${relationsContent},\n`;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
metaContent += '};\n';
|
|
58
|
-
|
|
59
|
-
fs.writeFileSync(this.outputPath, metaContent);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private getAllPropertyVariations(modelName: string): string[] {
|
|
63
|
-
const variations = new Set<string>();
|
|
64
|
-
|
|
65
|
-
// 1.
|
|
66
|
-
variations.add(
|
|
67
|
-
|
|
68
|
-
// 2.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Model, bracketedTableName } from './types';
|
|
4
|
+
|
|
5
|
+
export class MetadataGenerator {
|
|
6
|
+
constructor(
|
|
7
|
+
private outputPath: string,
|
|
8
|
+
private relationImport: string = 'import type { RelationDef } from "@an5/orm";'
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
public generate(models: Model[]) {
|
|
12
|
+
let metaContent = '// This file is auto-generated. Do not edit directly.\n\n';
|
|
13
|
+
metaContent += `${this.relationImport}\n\n`;
|
|
14
|
+
|
|
15
|
+
metaContent += 'export const modelToTable: Record<string, string> = {\n';
|
|
16
|
+
for (const model of models) {
|
|
17
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
18
|
+
const fullTableName = bracketedTableName(model);
|
|
19
|
+
for (const prop of props) {
|
|
20
|
+
metaContent += ` ${prop}: "${fullTableName}",\n`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
metaContent += '};\n\n';
|
|
24
|
+
|
|
25
|
+
metaContent += 'export const modelDescriptions: Record<string, string | undefined> = {\n';
|
|
26
|
+
for (const model of models) {
|
|
27
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
28
|
+
for (const prop of props) {
|
|
29
|
+
metaContent += ` ${prop}: ${model.description ? JSON.stringify(model.description) : 'undefined'},\n`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
metaContent += '};\n\n';
|
|
33
|
+
|
|
34
|
+
metaContent += 'export const modelFields: Record<string, Record<string, { ts: string; sql: string; description?: string }>> = {\n';
|
|
35
|
+
for (const model of models) {
|
|
36
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
37
|
+
const fieldsStr = `{ ${model.fields.map(f => `${f.name}: ${this.formatFieldMetadata(f)}`).join(', ')} }`;
|
|
38
|
+
for (const prop of props) {
|
|
39
|
+
metaContent += ` ${prop}: ${fieldsStr},\n`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
metaContent += '};\n\n';
|
|
43
|
+
|
|
44
|
+
metaContent += 'export const relationMap: Record<string, Record<string, RelationDef>> = {\n';
|
|
45
|
+
for (const model of models) {
|
|
46
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
47
|
+
let relationsContent = ` {\n`;
|
|
48
|
+
for (const rel of model.relations) {
|
|
49
|
+
relationsContent += ` ${rel.name}: { modelName: "${this.toCamelCase(rel.type)}", relationType: "${rel.isArray ? 'many' : 'one'}", foreignKey: "${rel.foreignKey || 'id'}", localKey: "${rel.localKey || 'id'}" },\n`;
|
|
50
|
+
}
|
|
51
|
+
relationsContent += ` }`;
|
|
52
|
+
|
|
53
|
+
for (const prop of props) {
|
|
54
|
+
metaContent += ` ${prop}: ${relationsContent},\n`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
metaContent += '};\n';
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(this.outputPath, metaContent);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private getAllPropertyVariations(modelName: string): string[] {
|
|
63
|
+
const variations = new Set<string>();
|
|
64
|
+
|
|
65
|
+
// 1. PascalCase (User)
|
|
66
|
+
variations.add(modelName);
|
|
67
|
+
|
|
68
|
+
// 2. camelCase (user)
|
|
69
|
+
variations.add(this.toCamelCase(modelName));
|
|
70
|
+
|
|
71
|
+
// 3. Plural PascalCase (Users)
|
|
72
|
+
variations.add(modelName + 's');
|
|
73
|
+
|
|
74
|
+
// 4. Plural camelCase (users)
|
|
75
|
+
variations.add(this.toCamelCase(modelName) + 's');
|
|
76
|
+
|
|
77
|
+
// 5. snake_case (user, mc_server)
|
|
78
|
+
variations.add(this.toSnakeCase(modelName));
|
|
79
|
+
variations.add(this.toSnakeCase(modelName) + 's');
|
|
80
|
+
|
|
81
|
+
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
82
|
+
for (const acronym of acronyms) {
|
|
83
|
+
if (modelName.startsWith(acronym)) {
|
|
84
|
+
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
|
|
85
|
+
variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
|
|
86
|
+
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length) + 's');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return Array.from(variations);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private toSnakeCase(str: string): string {
|
|
94
|
+
if (!str) return '';
|
|
95
|
+
return str
|
|
96
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
97
|
+
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
98
|
+
.toLowerCase();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private formatFieldMetadata(field: Model['fields'][number]): string {
|
|
102
|
+
const entries = [
|
|
103
|
+
`ts: ${JSON.stringify(`${field.type}${field.isOptional ? '?' : ''}`)}`,
|
|
104
|
+
`sql: ${JSON.stringify(field.sqlType)}`,
|
|
105
|
+
];
|
|
106
|
+
if (field.description) {
|
|
107
|
+
entries.push(`description: ${JSON.stringify(field.description)}`);
|
|
108
|
+
}
|
|
109
|
+
return `{ ${entries.join(', ')} }`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private toCamelCase(str: string): string {
|
|
113
|
+
if (!str) return '';
|
|
114
|
+
return str[0].toLowerCase() + str.slice(1);
|
|
115
|
+
}
|
|
116
|
+
}
|