@an5/orm 1.0.5 → 1.0.6
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 +123 -123
- package/dist/an5Orm.js +14 -14
- package/dist/cleanup.js +10 -7
- package/dist/cli.js +0 -0
- package/dist/generator/src/code-generator.js +39 -39
- package/dist/generator/src/dotnet-generator.js +436 -436
- package/dist/generator/src/index.js +5 -2
- package/dist/migrate.js +25 -22
- package/dist/pull.js +44 -41
- package/dist/push.js +4 -1
- package/dist/seed.js +123 -123
- package/generator/src/code-generator.ts +234 -234
- package/generator/src/dotnet-generator.ts +544 -544
- package/generator/src/index.ts +94 -91
- package/generator/src/metadata-generator.ts +66 -66
- package/generator/src/parser.ts +202 -202
- package/generator/src/python-generator.ts +74 -74
- package/generator/src/types.ts +29 -29
- package/package.json +53 -53
package/generator/src/index.ts
CHANGED
|
@@ -1,92 +1,95 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { SchemaParser } from './parser';
|
|
3
|
-
import { CodeGenerator } from './code-generator';
|
|
4
|
-
import { MetadataGenerator } from './metadata-generator';
|
|
5
|
-
import { PythonGenerator } from './python-generator';
|
|
6
|
-
import { DotnetGenerator } from './dotnet-generator';
|
|
7
|
-
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
|
|
10
|
-
function clearGeneratedFiles(outputDir: string, extension: string) {
|
|
11
|
-
if (!fs.existsSync(outputDir)) {
|
|
12
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
for (const entry of fs.readdirSync(outputDir)) {
|
|
17
|
-
const fullPath = path.join(outputDir, entry);
|
|
18
|
-
const stat = fs.statSync(fullPath);
|
|
19
|
-
if (stat.isDirectory()) continue;
|
|
20
|
-
if (entry.endsWith(extension)) {
|
|
21
|
-
fs.unlinkSync(fullPath);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function main() {
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { SchemaParser } from './parser';
|
|
3
|
+
import { CodeGenerator } from './code-generator';
|
|
4
|
+
import { MetadataGenerator } from './metadata-generator';
|
|
5
|
+
import { PythonGenerator } from './python-generator';
|
|
6
|
+
import { DotnetGenerator } from './dotnet-generator';
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
|
|
10
|
+
function clearGeneratedFiles(outputDir: string, extension: string) {
|
|
11
|
+
if (!fs.existsSync(outputDir)) {
|
|
12
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const entry of fs.readdirSync(outputDir)) {
|
|
17
|
+
const fullPath = path.join(outputDir, entry);
|
|
18
|
+
const stat = fs.statSync(fullPath);
|
|
19
|
+
if (stat.isDirectory()) continue;
|
|
20
|
+
if (entry.endsWith(extension)) {
|
|
21
|
+
fs.unlinkSync(fullPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
27
|
const rootDir = process.cwd();
|
|
28
|
-
let config: any = {};
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
|
|
32
|
-
if (fs.existsSync(configPath)) {
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (fs.existsSync(
|
|
54
|
-
fs.unlinkSync(
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
28
|
+
let config: any = {};
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
let configPath = path.join(rootDir, 'an5Orm.config.js');
|
|
32
|
+
if (!fs.existsSync(configPath)) {
|
|
33
|
+
configPath = path.join(rootDir, 'an5Orm.config.cjs');
|
|
34
|
+
}
|
|
35
|
+
if (fs.existsSync(configPath)) {
|
|
36
|
+
config = require(configPath);
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.warn('⚠️ Could not load an5Orm.config.js/.cjs, using defaults.', err);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const schemaDir = path.resolve(rootDir, config.schemaDir || 'an5Schema');
|
|
43
|
+
const outputTypesDir = path.resolve(rootDir, config.outputs?.typescript?.outputDir || 'an5Client/typescript');
|
|
44
|
+
const outputMetadataPath = path.resolve(rootDir, config.outputs?.typescript?.metadataFile || 'an5Client/typescript/an5Metadata.ts');
|
|
45
|
+
const outputPythonMetadataPath = path.resolve(rootDir, config.outputs?.python?.metadataFile || 'an5Client/python/an5_metadata.py');
|
|
46
|
+
const outputDotnetDir = path.resolve(rootDir, config.outputs?.dotnet?.outputDir || 'an5Client/dotnet');
|
|
47
|
+
|
|
48
|
+
console.log('🚀 Starting ORM generation...');
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
clearGeneratedFiles(outputTypesDir, '.ts');
|
|
52
|
+
clearGeneratedFiles(outputDotnetDir, '.cs');
|
|
53
|
+
if (fs.existsSync(outputMetadataPath)) {
|
|
54
|
+
fs.unlinkSync(outputMetadataPath);
|
|
55
|
+
}
|
|
56
|
+
if (fs.existsSync(outputPythonMetadataPath)) {
|
|
57
|
+
fs.unlinkSync(outputPythonMetadataPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parser = new SchemaParser(schemaDir);
|
|
61
|
+
const models = await parser.parse();
|
|
62
|
+
console.log(`📦 Parsed ${models.length} models from schema.`);
|
|
63
|
+
|
|
64
|
+
const codeGen = new CodeGenerator(outputTypesDir);
|
|
65
|
+
codeGen.generate(models);
|
|
66
|
+
console.log(`✨ Generated modular types in ${outputTypesDir}`);
|
|
67
|
+
|
|
68
|
+
const metadataDir = path.dirname(outputMetadataPath);
|
|
69
|
+
if (!fs.existsSync(metadataDir)) {
|
|
70
|
+
fs.mkdirSync(metadataDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
const metadataGen = new MetadataGenerator(outputMetadataPath);
|
|
73
|
+
metadataGen.generate(models);
|
|
74
|
+
console.log(`✨ Generated metadata in ${outputMetadataPath}`);
|
|
75
|
+
|
|
76
|
+
const pythonDir = path.dirname(outputPythonMetadataPath);
|
|
77
|
+
if (!fs.existsSync(pythonDir)) {
|
|
78
|
+
fs.mkdirSync(pythonDir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
const pythonGen = new PythonGenerator(outputPythonMetadataPath);
|
|
81
|
+
pythonGen.generate(models);
|
|
82
|
+
console.log(`✨ Generated Python metadata in ${outputPythonMetadataPath}`);
|
|
83
|
+
|
|
84
|
+
const dotnetGen = new DotnetGenerator(outputDotnetDir);
|
|
85
|
+
dotnetGen.generate(models);
|
|
86
|
+
console.log(`✨ Generated .NET models in ${outputDotnetDir}`);
|
|
87
|
+
|
|
88
|
+
console.log('✅ ORM generation completed successfully.');
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('❌ ORM generation failed:', error);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main();
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Model } from './types';
|
|
4
|
-
|
|
5
|
-
export class MetadataGenerator {
|
|
6
|
-
constructor(private outputPath: string) {}
|
|
7
|
-
|
|
8
|
-
public generate(models: Model[]) {
|
|
9
|
-
let metaContent = '// This file is auto-generated. Do not edit directly.\n\n';
|
|
10
|
-
|
|
11
|
-
metaContent += 'export const modelToTable: Record<string, string> = {\n';
|
|
12
|
-
for (const model of models) {
|
|
13
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
14
|
-
const fullTableName = `[${model.schemaName}].[${model.tableName}]`;
|
|
15
|
-
for (const prop of props) {
|
|
16
|
-
metaContent += ` ${prop}: "${fullTableName}",\n`;
|
|
17
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Model } from './types';
|
|
4
|
+
|
|
5
|
+
export class MetadataGenerator {
|
|
6
|
+
constructor(private outputPath: string) {}
|
|
7
|
+
|
|
8
|
+
public generate(models: Model[]) {
|
|
9
|
+
let metaContent = '// This file is auto-generated. Do not edit directly.\n\n';
|
|
10
|
+
|
|
11
|
+
metaContent += 'export const modelToTable: Record<string, string> = {\n';
|
|
12
|
+
for (const model of models) {
|
|
13
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
14
|
+
const fullTableName = `[${model.schemaName}].[${model.tableName}]`;
|
|
15
|
+
for (const prop of props) {
|
|
16
|
+
metaContent += ` ${prop}: "${fullTableName}",\n`;
|
|
17
|
+
}
|
|
18
18
|
}
|
|
19
19
|
metaContent += '};\n\n';
|
|
20
20
|
|
|
@@ -31,52 +31,52 @@ export class MetadataGenerator {
|
|
|
31
31
|
for (const model of models) {
|
|
32
32
|
const props = this.getAllPropertyVariations(model.name);
|
|
33
33
|
const fieldsStr = `{ ${model.fields.map(f => `${f.name}: ${this.formatFieldMetadata(f)}`).join(', ')} }`;
|
|
34
|
-
for (const prop of props) {
|
|
35
|
-
metaContent += ` ${prop}: ${fieldsStr},\n`;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
metaContent += '};\n\n';
|
|
39
|
-
|
|
40
|
-
metaContent += 'export interface RelationDef {\n modelName: string;\n relationType: "many" | "one";\n foreignKey: string;\n localKey: string;\n}\n\n';
|
|
41
|
-
|
|
42
|
-
metaContent += 'export const relationMap: Record<string, Record<string, RelationDef>> = {\n';
|
|
43
|
-
for (const model of models) {
|
|
44
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
45
|
-
let relationsContent = ` {\n`;
|
|
46
|
-
for (const rel of model.relations) {
|
|
47
|
-
relationsContent += ` ${rel.name}: { modelName: "${this.toCamelCase(rel.type)}", relationType: "${rel.isArray ? 'many' : 'one'}", foreignKey: "${rel.foreignKey || 'id'}", localKey: "${rel.localKey || 'id'}" },\n`;
|
|
48
|
-
}
|
|
49
|
-
relationsContent += ` }`;
|
|
50
|
-
|
|
51
|
-
for (const prop of props) {
|
|
52
|
-
metaContent += ` ${prop}: ${relationsContent},\n`;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
metaContent += '};\n';
|
|
56
|
-
|
|
57
|
-
fs.writeFileSync(this.outputPath, metaContent);
|
|
58
|
-
}
|
|
59
|
-
|
|
34
|
+
for (const prop of props) {
|
|
35
|
+
metaContent += ` ${prop}: ${fieldsStr},\n`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
metaContent += '};\n\n';
|
|
39
|
+
|
|
40
|
+
metaContent += 'export interface RelationDef {\n modelName: string;\n relationType: "many" | "one";\n foreignKey: string;\n localKey: string;\n}\n\n';
|
|
41
|
+
|
|
42
|
+
metaContent += 'export const relationMap: Record<string, Record<string, RelationDef>> = {\n';
|
|
43
|
+
for (const model of models) {
|
|
44
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
45
|
+
let relationsContent = ` {\n`;
|
|
46
|
+
for (const rel of model.relations) {
|
|
47
|
+
relationsContent += ` ${rel.name}: { modelName: "${this.toCamelCase(rel.type)}", relationType: "${rel.isArray ? 'many' : 'one'}", foreignKey: "${rel.foreignKey || 'id'}", localKey: "${rel.localKey || 'id'}" },\n`;
|
|
48
|
+
}
|
|
49
|
+
relationsContent += ` }`;
|
|
50
|
+
|
|
51
|
+
for (const prop of props) {
|
|
52
|
+
metaContent += ` ${prop}: ${relationsContent},\n`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
metaContent += '};\n';
|
|
56
|
+
|
|
57
|
+
fs.writeFileSync(this.outputPath, metaContent);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
60
|
private getAllPropertyVariations(modelName: string): string[] {
|
|
61
|
-
const variations = new Set<string>();
|
|
62
|
-
|
|
63
|
-
// 1. Standard camelCase (e.g. User -> user, McpServer -> mcpServer)
|
|
64
|
-
variations.add(this.toCamelCase(modelName));
|
|
65
|
-
|
|
66
|
-
// 2. Handle known acronyms at start (e.g. LLMProvider -> lLMProvider)
|
|
67
|
-
// This is already handled by toCamelCase if it only lowercases the first letter.
|
|
68
|
-
// But we might want 'llmProvider' as well.
|
|
69
|
-
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
70
|
-
|
|
71
|
-
for (const acronym of acronyms) {
|
|
72
|
-
if (modelName.startsWith(acronym)) {
|
|
73
|
-
// e.g. LLMProvider -> llmProvider
|
|
74
|
-
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
|
|
75
|
-
|
|
76
|
-
// e.g. LLMProvider -> lLMProvider (Prisma style)
|
|
77
|
-
variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
61
|
+
const variations = new Set<string>();
|
|
62
|
+
|
|
63
|
+
// 1. Standard camelCase (e.g. User -> user, McpServer -> mcpServer)
|
|
64
|
+
variations.add(this.toCamelCase(modelName));
|
|
65
|
+
|
|
66
|
+
// 2. Handle known acronyms at start (e.g. LLMProvider -> lLMProvider)
|
|
67
|
+
// This is already handled by toCamelCase if it only lowercases the first letter.
|
|
68
|
+
// But we might want 'llmProvider' as well.
|
|
69
|
+
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
70
|
+
|
|
71
|
+
for (const acronym of acronyms) {
|
|
72
|
+
if (modelName.startsWith(acronym)) {
|
|
73
|
+
// e.g. LLMProvider -> llmProvider
|
|
74
|
+
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
|
|
75
|
+
|
|
76
|
+
// e.g. LLMProvider -> lLMProvider (Prisma style)
|
|
77
|
+
variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
80
|
|
|
81
81
|
return Array.from(variations);
|
|
82
82
|
}
|
|
@@ -93,7 +93,7 @@ export class MetadataGenerator {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
private toCamelCase(str: string): string {
|
|
96
|
-
if (!str) return '';
|
|
97
|
-
return str[0].toLowerCase() + str.slice(1);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
96
|
+
if (!str) return '';
|
|
97
|
+
return str[0].toLowerCase() + str.slice(1);
|
|
98
|
+
}
|
|
99
|
+
}
|