@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/parser.ts
CHANGED
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Model, Field, Relation } from './types';
|
|
4
|
-
|
|
5
|
-
// SQL Server type → TypeScript type mapping
|
|
6
|
-
const AN5_TO_TS: Record<string, string> = {
|
|
7
|
-
// String types
|
|
8
|
-
'NVARCHAR': 'string',
|
|
9
|
-
'VARCHAR': 'string',
|
|
10
|
-
'CHAR': 'string',
|
|
11
|
-
'NCHAR': 'string',
|
|
12
|
-
'TEXT': 'string',
|
|
13
|
-
'NTEXT': 'string',
|
|
14
|
-
'XML': 'string',
|
|
15
|
-
// Numeric types
|
|
16
|
-
'INT': 'number',
|
|
17
|
-
'SMALLINT': 'number',
|
|
18
|
-
'TINYINT': 'number',
|
|
19
|
-
'BIGINT': 'number | bigint',
|
|
20
|
-
'FLOAT': 'number',
|
|
21
|
-
'REAL': 'number',
|
|
22
|
-
'DECIMAL': 'number',
|
|
23
|
-
'NUMERIC': 'number',
|
|
24
|
-
'MONEY': 'number',
|
|
25
|
-
'SMALLMONEY': 'number',
|
|
26
|
-
// Boolean
|
|
27
|
-
'BIT': 'boolean',
|
|
28
|
-
// Date types
|
|
29
|
-
'DATETIME': 'Date',
|
|
30
|
-
'DATETIME2': 'Date',
|
|
31
|
-
'SMALLDATETIME': 'Date',
|
|
32
|
-
'DATE': 'Date',
|
|
33
|
-
'TIME': 'Date',
|
|
34
|
-
'DATETIMEOFFSET': 'Date',
|
|
35
|
-
// Binary types
|
|
36
|
-
'VARBINARY': 'Buffer',
|
|
37
|
-
'BINARY': 'Buffer',
|
|
38
|
-
'IMAGE': 'Buffer',
|
|
39
|
-
// Other
|
|
40
|
-
'UNIQUEIDENTIFIER': 'string',
|
|
41
|
-
'SQL_VARIANT': 'any',
|
|
42
|
-
'ROWVERSION': 'Buffer',
|
|
43
|
-
'HIERARCHYID': 'string',
|
|
44
|
-
'GEOGRAPHY': 'string',
|
|
45
|
-
'GEOMETRY': 'string',
|
|
46
|
-
'VECTOR': 'number[] | string',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// Parse base type from "NVARCHAR(255)" → "NVARCHAR"
|
|
50
|
-
function parseSqlType(raw: string): { base: string; params: string } {
|
|
51
|
-
const match = raw.match(/^(\w+)(?:\((.+)\))?$/);
|
|
52
|
-
if (!match) return { base: raw, params: '' };
|
|
53
|
-
return { base: match[1].toUpperCase(), params: match[2] || '' };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Map SQL Server type to TypeScript type
|
|
57
|
-
export function sqlTypeToTs(sqlType: string): string {
|
|
58
|
-
const { base } = parseSqlType(sqlType);
|
|
59
|
-
return AN5_TO_TS[base] || 'any';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export class SchemaParser {
|
|
63
|
-
private schemaText: string = '';
|
|
64
|
-
|
|
65
|
-
constructor(private schemaDir: string) {}
|
|
66
|
-
|
|
67
|
-
public async parse(): Promise<Model[]> {
|
|
68
|
-
this.loadSchema();
|
|
69
|
-
const lines = this.schemaText.split('\n');
|
|
70
|
-
const models: Model[] = [];
|
|
71
|
-
let currentModel: Model | null = null;
|
|
72
|
-
|
|
73
|
-
for (let line of lines) {
|
|
74
|
-
line = line.trim();
|
|
75
|
-
if (!line || line.startsWith('//')) continue;
|
|
76
|
-
|
|
77
|
-
const modelHeaderMatch = line.match(/^model\s+(\w+)\s*\{/);
|
|
78
|
-
if (modelHeaderMatch) {
|
|
79
|
-
const modelName = modelHeaderMatch[1];
|
|
80
|
-
currentModel = {
|
|
81
|
-
name: modelName,
|
|
82
|
-
tableName: modelName.toLowerCase() + 's',
|
|
83
|
-
schemaName: 'dbo',
|
|
84
|
-
fields: [],
|
|
85
|
-
relations: []
|
|
86
|
-
};
|
|
87
|
-
models.push(currentModel);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (line === '}') {
|
|
92
|
-
currentModel = null;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (currentModel) {
|
|
97
|
-
this.parseModelLine(line, currentModel);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
this.postProcessRelations(models);
|
|
102
|
-
return models;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private loadSchema() {
|
|
106
|
-
if (fs.existsSync(this.schemaDir)) {
|
|
107
|
-
const files = fs.readdirSync(this.schemaDir).filter(f => f.endsWith('.an5'));
|
|
108
|
-
for (const file of files) {
|
|
109
|
-
this.schemaText += fs.readFileSync(path.join(this.schemaDir, file), 'utf8') + '\n';
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
throw new Error(`No schema directory found at ${this.schemaDir}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private parseModelLine(line: string, model: Model) {
|
|
117
|
-
if (line.startsWith('@@map')) {
|
|
118
|
-
const mapMatch = line.match(/@@map\("(.+)"\)/);
|
|
119
|
-
if (mapMatch) model.tableName = mapMatch[1];
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (line.startsWith('@@schema')) {
|
|
123
|
-
const schemaMatch = line.match(/@@schema\("(.+)"\)/);
|
|
124
|
-
if (schemaMatch) model.schemaName = schemaMatch[1];
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (line.startsWith('@@unique')) {
|
|
128
|
-
const uniqueMatch = line.match(/@@unique\(\[([\w,\s]+)\]\)/);
|
|
129
|
-
if (uniqueMatch) {
|
|
130
|
-
const fields = uniqueMatch[1].split(',').map(f => f.trim());
|
|
131
|
-
model.compoundUniques = model.compoundUniques || [];
|
|
132
|
-
model.compoundUniques.push(fields);
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (line.startsWith('@@description')) {
|
|
137
|
-
const descMatch = line.match(/@@description\("(.+)"\)/);
|
|
138
|
-
if (descMatch) model.description = descMatch[1];
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (line.startsWith('@@')) return;
|
|
142
|
-
|
|
143
|
-
const parts = line.split(/\s+/);
|
|
144
|
-
const fieldName = parts[0];
|
|
145
|
-
let fieldType = parts[1];
|
|
146
|
-
if (!fieldName || !fieldType) return;
|
|
147
|
-
|
|
148
|
-
const isArray = fieldType.endsWith('[]');
|
|
149
|
-
const isOptional = fieldType.endsWith('?');
|
|
150
|
-
const cleanType = fieldType.replace('[]', '').replace('?', '');
|
|
151
|
-
|
|
152
|
-
// Parse SQL Server type (e.g., "NVARCHAR(255)" → base="NVARCHAR")
|
|
153
|
-
const { base: sqlBase } = parseSqlType(cleanType);
|
|
154
|
-
|
|
155
|
-
let tsType = 'any';
|
|
156
|
-
let isRelation = false;
|
|
157
|
-
|
|
158
|
-
// Check if it's a known SQL Server type
|
|
159
|
-
if (AN5_TO_TS[sqlBase]) {
|
|
160
|
-
tsType = sqlTypeToTs(cleanType);
|
|
161
|
-
} else if (cleanType[0] === cleanType[0].toUpperCase() && !cleanType.includes('(')) {
|
|
162
|
-
// Uppercase without parens = likely a relation to another model
|
|
163
|
-
tsType = cleanType;
|
|
164
|
-
isRelation = true;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (isRelation) {
|
|
168
|
-
let foreignKey = '', localKey = '', relationName = '';
|
|
169
|
-
const nameMatch = line.match(/@relation\("(\w+)"/);
|
|
170
|
-
if (nameMatch) relationName = nameMatch[1];
|
|
171
|
-
const relationMatch = line.match(/@relation\((?:.*fields:\s*\[(\w+)\],)?\s*(?:.*references:\s*\[(\w+)\],?)?.*\)/);
|
|
172
|
-
if (relationMatch) {
|
|
173
|
-
foreignKey = relationMatch[1] || '';
|
|
174
|
-
localKey = relationMatch[2] || '';
|
|
175
|
-
}
|
|
176
|
-
model.relations.push({ name: fieldName, type: tsType, isArray, isOptional, foreignKey, localKey, relationName });
|
|
177
|
-
} else {
|
|
178
|
-
const hasDefault = line.includes('@default') || line.includes('@updatedAt') || line.includes('@id');
|
|
179
|
-
const isId = line.includes('@id');
|
|
180
|
-
let description: string | undefined;
|
|
181
|
-
const descMatch = line.match(/@description\("(.+)"\)/);
|
|
182
|
-
if (descMatch) description = descMatch[1];
|
|
183
|
-
model.fields.push({ name: fieldName, type: tsType, sqlType: cleanType, isOptional, hasDefault, isId, description });
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private postProcessRelations(models: Model[]) {
|
|
188
|
-
for (const model of models) {
|
|
189
|
-
for (const rel of model.relations) {
|
|
190
|
-
if (!rel.foreignKey || !rel.localKey) {
|
|
191
|
-
const targetModel = models.find(m => m.name === rel.type);
|
|
192
|
-
if (targetModel) {
|
|
193
|
-
let opposite = rel.relationName ?
|
|
194
|
-
targetModel.relations.find(r => r.type === model.name && r.relationName === rel.relationName && r.foreignKey && r.localKey) :
|
|
195
|
-
targetModel.relations.find(r => r.type === model.name && r.foreignKey && r.localKey);
|
|
196
|
-
if (opposite) { rel.foreignKey = opposite.foreignKey; rel.localKey = opposite.localKey; }
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Model, Field, Relation } from './types';
|
|
4
|
+
|
|
5
|
+
// SQL Server type → TypeScript type mapping
|
|
6
|
+
const AN5_TO_TS: Record<string, string> = {
|
|
7
|
+
// String types
|
|
8
|
+
'NVARCHAR': 'string',
|
|
9
|
+
'VARCHAR': 'string',
|
|
10
|
+
'CHAR': 'string',
|
|
11
|
+
'NCHAR': 'string',
|
|
12
|
+
'TEXT': 'string',
|
|
13
|
+
'NTEXT': 'string',
|
|
14
|
+
'XML': 'string',
|
|
15
|
+
// Numeric types
|
|
16
|
+
'INT': 'number',
|
|
17
|
+
'SMALLINT': 'number',
|
|
18
|
+
'TINYINT': 'number',
|
|
19
|
+
'BIGINT': 'number | bigint',
|
|
20
|
+
'FLOAT': 'number',
|
|
21
|
+
'REAL': 'number',
|
|
22
|
+
'DECIMAL': 'number',
|
|
23
|
+
'NUMERIC': 'number',
|
|
24
|
+
'MONEY': 'number',
|
|
25
|
+
'SMALLMONEY': 'number',
|
|
26
|
+
// Boolean
|
|
27
|
+
'BIT': 'boolean',
|
|
28
|
+
// Date types
|
|
29
|
+
'DATETIME': 'Date',
|
|
30
|
+
'DATETIME2': 'Date',
|
|
31
|
+
'SMALLDATETIME': 'Date',
|
|
32
|
+
'DATE': 'Date',
|
|
33
|
+
'TIME': 'Date',
|
|
34
|
+
'DATETIMEOFFSET': 'Date',
|
|
35
|
+
// Binary types
|
|
36
|
+
'VARBINARY': 'Buffer',
|
|
37
|
+
'BINARY': 'Buffer',
|
|
38
|
+
'IMAGE': 'Buffer',
|
|
39
|
+
// Other
|
|
40
|
+
'UNIQUEIDENTIFIER': 'string',
|
|
41
|
+
'SQL_VARIANT': 'any',
|
|
42
|
+
'ROWVERSION': 'Buffer',
|
|
43
|
+
'HIERARCHYID': 'string',
|
|
44
|
+
'GEOGRAPHY': 'string',
|
|
45
|
+
'GEOMETRY': 'string',
|
|
46
|
+
'VECTOR': 'number[] | string',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Parse base type from "NVARCHAR(255)" → "NVARCHAR"
|
|
50
|
+
function parseSqlType(raw: string): { base: string; params: string } {
|
|
51
|
+
const match = raw.match(/^(\w+)(?:\((.+)\))?$/);
|
|
52
|
+
if (!match) return { base: raw, params: '' };
|
|
53
|
+
return { base: match[1].toUpperCase(), params: match[2] || '' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Map SQL Server type to TypeScript type
|
|
57
|
+
export function sqlTypeToTs(sqlType: string): string {
|
|
58
|
+
const { base } = parseSqlType(sqlType);
|
|
59
|
+
return AN5_TO_TS[base] || 'any';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class SchemaParser {
|
|
63
|
+
private schemaText: string = '';
|
|
64
|
+
|
|
65
|
+
constructor(private schemaDir: string) {}
|
|
66
|
+
|
|
67
|
+
public async parse(): Promise<Model[]> {
|
|
68
|
+
this.loadSchema();
|
|
69
|
+
const lines = this.schemaText.split('\n');
|
|
70
|
+
const models: Model[] = [];
|
|
71
|
+
let currentModel: Model | null = null;
|
|
72
|
+
|
|
73
|
+
for (let line of lines) {
|
|
74
|
+
line = line.trim();
|
|
75
|
+
if (!line || line.startsWith('//')) continue;
|
|
76
|
+
|
|
77
|
+
const modelHeaderMatch = line.match(/^model\s+(\w+)\s*\{/);
|
|
78
|
+
if (modelHeaderMatch) {
|
|
79
|
+
const modelName = modelHeaderMatch[1];
|
|
80
|
+
currentModel = {
|
|
81
|
+
name: modelName,
|
|
82
|
+
tableName: modelName.toLowerCase() + 's',
|
|
83
|
+
schemaName: 'dbo',
|
|
84
|
+
fields: [],
|
|
85
|
+
relations: []
|
|
86
|
+
};
|
|
87
|
+
models.push(currentModel);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (line === '}') {
|
|
92
|
+
currentModel = null;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (currentModel) {
|
|
97
|
+
this.parseModelLine(line, currentModel);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.postProcessRelations(models);
|
|
102
|
+
return models;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private loadSchema() {
|
|
106
|
+
if (fs.existsSync(this.schemaDir)) {
|
|
107
|
+
const files = fs.readdirSync(this.schemaDir).filter(f => f.endsWith('.an5'));
|
|
108
|
+
for (const file of files) {
|
|
109
|
+
this.schemaText += fs.readFileSync(path.join(this.schemaDir, file), 'utf8') + '\n';
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error(`No schema directory found at ${this.schemaDir}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private parseModelLine(line: string, model: Model) {
|
|
117
|
+
if (line.startsWith('@@map')) {
|
|
118
|
+
const mapMatch = line.match(/@@map\("(.+)"\)/);
|
|
119
|
+
if (mapMatch) model.tableName = mapMatch[1];
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (line.startsWith('@@schema')) {
|
|
123
|
+
const schemaMatch = line.match(/@@schema\("(.+)"\)/);
|
|
124
|
+
if (schemaMatch) model.schemaName = schemaMatch[1];
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (line.startsWith('@@unique')) {
|
|
128
|
+
const uniqueMatch = line.match(/@@unique\(\[([\w,\s]+)\]\)/);
|
|
129
|
+
if (uniqueMatch) {
|
|
130
|
+
const fields = uniqueMatch[1].split(',').map(f => f.trim());
|
|
131
|
+
model.compoundUniques = model.compoundUniques || [];
|
|
132
|
+
model.compoundUniques.push(fields);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (line.startsWith('@@description')) {
|
|
137
|
+
const descMatch = line.match(/@@description\("(.+)"\)/);
|
|
138
|
+
if (descMatch) model.description = descMatch[1];
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (line.startsWith('@@')) return;
|
|
142
|
+
|
|
143
|
+
const parts = line.split(/\s+/);
|
|
144
|
+
const fieldName = parts[0];
|
|
145
|
+
let fieldType = parts[1];
|
|
146
|
+
if (!fieldName || !fieldType) return;
|
|
147
|
+
|
|
148
|
+
const isArray = fieldType.endsWith('[]');
|
|
149
|
+
const isOptional = fieldType.endsWith('?');
|
|
150
|
+
const cleanType = fieldType.replace('[]', '').replace('?', '');
|
|
151
|
+
|
|
152
|
+
// Parse SQL Server type (e.g., "NVARCHAR(255)" → base="NVARCHAR")
|
|
153
|
+
const { base: sqlBase } = parseSqlType(cleanType);
|
|
154
|
+
|
|
155
|
+
let tsType = 'any';
|
|
156
|
+
let isRelation = false;
|
|
157
|
+
|
|
158
|
+
// Check if it's a known SQL Server type
|
|
159
|
+
if (AN5_TO_TS[sqlBase]) {
|
|
160
|
+
tsType = sqlTypeToTs(cleanType);
|
|
161
|
+
} else if (cleanType[0] === cleanType[0].toUpperCase() && !cleanType.includes('(')) {
|
|
162
|
+
// Uppercase without parens = likely a relation to another model
|
|
163
|
+
tsType = cleanType;
|
|
164
|
+
isRelation = true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isRelation) {
|
|
168
|
+
let foreignKey = '', localKey = '', relationName = '';
|
|
169
|
+
const nameMatch = line.match(/@relation\("(\w+)"/);
|
|
170
|
+
if (nameMatch) relationName = nameMatch[1];
|
|
171
|
+
const relationMatch = line.match(/@relation\((?:.*fields:\s*\[(\w+)\],)?\s*(?:.*references:\s*\[(\w+)\],?)?.*\)/);
|
|
172
|
+
if (relationMatch) {
|
|
173
|
+
foreignKey = relationMatch[1] || '';
|
|
174
|
+
localKey = relationMatch[2] || '';
|
|
175
|
+
}
|
|
176
|
+
model.relations.push({ name: fieldName, type: tsType, isArray, isOptional, foreignKey, localKey, relationName });
|
|
177
|
+
} else {
|
|
178
|
+
const hasDefault = line.includes('@default') || line.includes('@updatedAt') || line.includes('@id');
|
|
179
|
+
const isId = line.includes('@id');
|
|
180
|
+
let description: string | undefined;
|
|
181
|
+
const descMatch = line.match(/@description\("(.+)"\)/);
|
|
182
|
+
if (descMatch) description = descMatch[1];
|
|
183
|
+
model.fields.push({ name: fieldName, type: tsType, sqlType: cleanType, isOptional, hasDefault, isId, description });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private postProcessRelations(models: Model[]) {
|
|
188
|
+
for (const model of models) {
|
|
189
|
+
for (const rel of model.relations) {
|
|
190
|
+
if (!rel.foreignKey || !rel.localKey) {
|
|
191
|
+
const targetModel = models.find(m => m.name === rel.type);
|
|
192
|
+
if (targetModel) {
|
|
193
|
+
let opposite = rel.relationName ?
|
|
194
|
+
targetModel.relations.find(r => r.type === model.name && r.relationName === rel.relationName && r.foreignKey && r.localKey) :
|
|
195
|
+
targetModel.relations.find(r => r.type === model.name && r.foreignKey && r.localKey);
|
|
196
|
+
if (opposite) { rel.foreignKey = opposite.foreignKey; rel.localKey = opposite.localKey; }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Model } from './types';
|
|
4
|
-
|
|
5
|
-
export class PythonGenerator {
|
|
6
|
-
constructor(private outputPath: string) {}
|
|
7
|
-
|
|
8
|
-
public generate(models: Model[]) {
|
|
9
|
-
let pyContent = '# This file is auto-generated. Do not edit directly.\n\n';
|
|
10
|
-
|
|
11
|
-
// 1. MODEL_TO_TABLE
|
|
12
|
-
pyContent += 'MODEL_TO_TABLE = {\n';
|
|
13
|
-
for (const model of models) {
|
|
14
|
-
const props = this.getAllPropertyVariations(model.name);
|
|
15
|
-
const fullTableName = `[${model.schemaName}].[${model.tableName}]`;
|
|
16
|
-
for (const prop of props) {
|
|
17
|
-
pyContent += ` "${prop}": "${fullTableName}",\n`;
|
|
18
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { Model } from './types';
|
|
4
|
+
|
|
5
|
+
export class PythonGenerator {
|
|
6
|
+
constructor(private outputPath: string) {}
|
|
7
|
+
|
|
8
|
+
public generate(models: Model[]) {
|
|
9
|
+
let pyContent = '# This file is auto-generated. Do not edit directly.\n\n';
|
|
10
|
+
|
|
11
|
+
// 1. MODEL_TO_TABLE
|
|
12
|
+
pyContent += 'MODEL_TO_TABLE = {\n';
|
|
13
|
+
for (const model of models) {
|
|
14
|
+
const props = this.getAllPropertyVariations(model.name);
|
|
15
|
+
const fullTableName = `[${model.schemaName}].[${model.tableName}]`;
|
|
16
|
+
for (const prop of props) {
|
|
17
|
+
pyContent += ` "${prop}": "${fullTableName}",\n`;
|
|
18
|
+
}
|
|
19
19
|
}
|
|
20
20
|
pyContent += '}\n\n';
|
|
21
21
|
|
|
@@ -37,52 +37,52 @@ export class PythonGenerator {
|
|
|
37
37
|
for (const prop of props) {
|
|
38
38
|
pyContent += ` "${prop}": ${fieldsStr},\n`;
|
|
39
39
|
}
|
|
40
|
-
}
|
|
41
|
-
pyContent += '}\n\n';
|
|
42
|
-
|
|
40
|
+
}
|
|
41
|
+
pyContent += '}\n\n';
|
|
42
|
+
|
|
43
43
|
// 4. RELATION_MAP
|
|
44
|
-
pyContent += 'RELATION_MAP = {\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}": {\n`;
|
|
50
|
-
relationsContent += ` "modelName": "${this.toCamelCase(rel.type)}",\n`;
|
|
51
|
-
relationsContent += ` "relationType": "${rel.isArray ? 'many' : 'one'}",\n`;
|
|
52
|
-
relationsContent += ` "foreignKey": "${rel.foreignKey || 'id'}",\n`;
|
|
53
|
-
relationsContent += ` "localKey": "${rel.localKey || 'id'}"\n`;
|
|
54
|
-
relationsContent += ` },\n`;
|
|
55
|
-
}
|
|
56
|
-
relationsContent += ` }`;
|
|
57
|
-
|
|
58
|
-
for (const prop of props) {
|
|
59
|
-
pyContent += ` "${prop}": ${relationsContent},\n`;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
pyContent += '}\n';
|
|
63
|
-
|
|
64
|
-
fs.writeFileSync(this.outputPath, pyContent);
|
|
65
|
-
}
|
|
66
|
-
|
|
44
|
+
pyContent += 'RELATION_MAP = {\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}": {\n`;
|
|
50
|
+
relationsContent += ` "modelName": "${this.toCamelCase(rel.type)}",\n`;
|
|
51
|
+
relationsContent += ` "relationType": "${rel.isArray ? 'many' : 'one'}",\n`;
|
|
52
|
+
relationsContent += ` "foreignKey": "${rel.foreignKey || 'id'}",\n`;
|
|
53
|
+
relationsContent += ` "localKey": "${rel.localKey || 'id'}"\n`;
|
|
54
|
+
relationsContent += ` },\n`;
|
|
55
|
+
}
|
|
56
|
+
relationsContent += ` }`;
|
|
57
|
+
|
|
58
|
+
for (const prop of props) {
|
|
59
|
+
pyContent += ` "${prop}": ${relationsContent},\n`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
pyContent += '}\n';
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(this.outputPath, pyContent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
67
|
private getAllPropertyVariations(modelName: string): string[] {
|
|
68
|
-
const variations = new Set<string>();
|
|
69
|
-
|
|
70
|
-
// 1. camelCase (User -> user, McpServer -> mcpServer)
|
|
71
|
-
variations.add(this.toCamelCase(modelName));
|
|
72
|
-
|
|
73
|
-
// 2. snake_case (User -> user, McpServer -> mcp_server)
|
|
74
|
-
variations.add(this.toSnakeCase(modelName));
|
|
75
|
-
|
|
76
|
-
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
77
|
-
for (const acronym of acronyms) {
|
|
78
|
-
if (modelName.startsWith(acronym)) {
|
|
79
|
-
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
|
|
80
|
-
variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
|
|
81
|
-
variations.add(this.toSnakeCase(acronym.toLowerCase() + modelName.slice(acronym.length)));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return Array.from(variations);
|
|
68
|
+
const variations = new Set<string>();
|
|
69
|
+
|
|
70
|
+
// 1. camelCase (User -> user, McpServer -> mcpServer)
|
|
71
|
+
variations.add(this.toCamelCase(modelName));
|
|
72
|
+
|
|
73
|
+
// 2. snake_case (User -> user, McpServer -> mcp_server)
|
|
74
|
+
variations.add(this.toSnakeCase(modelName));
|
|
75
|
+
|
|
76
|
+
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
77
|
+
for (const acronym of acronyms) {
|
|
78
|
+
if (modelName.startsWith(acronym)) {
|
|
79
|
+
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
|
|
80
|
+
variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
|
|
81
|
+
variations.add(this.toSnakeCase(acronym.toLowerCase() + modelName.slice(acronym.length)));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Array.from(variations);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
private formatFields(model: Model): string {
|
|
@@ -100,15 +100,15 @@ export class PythonGenerator {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
private toCamelCase(str: string): string {
|
|
103
|
-
if (!str) return '';
|
|
104
|
-
return str[0].toLowerCase() + str.slice(1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private toSnakeCase(str: string): string {
|
|
108
|
-
if (!str) return '';
|
|
109
|
-
return str
|
|
110
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
111
|
-
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
112
|
-
.toLowerCase();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
103
|
+
if (!str) return '';
|
|
104
|
+
return str[0].toLowerCase() + str.slice(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private toSnakeCase(str: string): string {
|
|
108
|
+
if (!str) return '';
|
|
109
|
+
return str
|
|
110
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
111
|
+
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
112
|
+
.toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
}
|
package/generator/src/types.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
export interface Field {
|
|
2
|
-
name: string;
|
|
3
|
-
type: string; // TypeScript type (string, number, boolean, Date, etc.)
|
|
4
|
-
sqlType: string; // SQL Server type (NVARCHAR(255), INT, DATETIME2, etc.)
|
|
5
|
-
isOptional: boolean;
|
|
6
|
-
hasDefault: boolean;
|
|
7
|
-
isId: boolean;
|
|
8
|
-
description?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface Relation {
|
|
12
|
-
name: string;
|
|
13
|
-
type: string;
|
|
14
|
-
isArray: boolean;
|
|
15
|
-
isOptional: boolean;
|
|
16
|
-
foreignKey: string;
|
|
17
|
-
localKey: string;
|
|
18
|
-
relationName: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface Model {
|
|
22
|
-
name: string;
|
|
23
|
-
tableName: string;
|
|
24
|
-
schemaName: string;
|
|
25
|
-
fields: Field[];
|
|
26
|
-
relations: Relation[];
|
|
27
|
-
compoundUniques?: string[][];
|
|
28
|
-
description?: string;
|
|
29
|
-
}
|
|
1
|
+
export interface Field {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string; // TypeScript type (string, number, boolean, Date, etc.)
|
|
4
|
+
sqlType: string; // SQL Server type (NVARCHAR(255), INT, DATETIME2, etc.)
|
|
5
|
+
isOptional: boolean;
|
|
6
|
+
hasDefault: boolean;
|
|
7
|
+
isId: boolean;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Relation {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
isArray: boolean;
|
|
15
|
+
isOptional: boolean;
|
|
16
|
+
foreignKey: string;
|
|
17
|
+
localKey: string;
|
|
18
|
+
relationName: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Model {
|
|
22
|
+
name: string;
|
|
23
|
+
tableName: string;
|
|
24
|
+
schemaName: string;
|
|
25
|
+
fields: Field[];
|
|
26
|
+
relations: Relation[];
|
|
27
|
+
compoundUniques?: string[][];
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|