@an5/orm 1.0.9 → 1.1.1
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 +10 -3
- package/dist/config.d.ts +9 -1
- package/dist/config.js +13 -18
- package/dist/generator/src/api.d.ts +35 -0
- package/dist/generator/src/api.js +58 -0
- 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 +8 -0
- package/dist/generator/src/dotnet-generator.js +282 -101
- 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 +1230 -0
- package/dist/generator/src/index.js +69 -36
- package/dist/generator/src/metadata-generator.js +2 -1
- package/dist/generator/src/parser.d.ts +28 -2
- package/dist/generator/src/parser.js +92 -90
- 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 +19 -1
- package/dist/generator/src/python-generator.js +147 -64
- 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 +33 -0
- package/dist/generator/src/types.js +39 -0
- package/dist/migration-core.d.ts +16 -1
- package/dist/migration-core.js +60 -21
- 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/sql-utils.js +8 -3
- package/generator/src/api.ts +45 -0
- package/generator/src/config.ts +496 -0
- package/generator/src/dialect.ts +415 -0
- package/generator/src/dotnet-generator.ts +277 -88
- package/generator/src/field-types.ts +473 -0
- package/generator/src/golang-generator.ts +94 -68
- package/generator/src/index.ts +71 -36
- package/generator/src/metadata-generator.ts +2 -2
- package/generator/src/parser.ts +122 -90
- package/generator/src/push-apply.ts +148 -0
- package/generator/src/push-schema.ts +250 -0
- package/generator/src/python-generator.ts +118 -62
- 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 +48 -0
- package/package.json +26 -6
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { Model, Field, Relation } from './types';
|
|
3
|
+
import { Model, Field, Relation, bracketedTableName, dottedTableName } from './types';
|
|
4
|
+
import { fieldKind } from './type-kinds';
|
|
5
|
+
import { Provider } from './field-types';
|
|
4
6
|
|
|
5
7
|
export class GolangGenerator {
|
|
8
|
+
/** The database being generated for; decides types like `TIMESTAMP`. */
|
|
9
|
+
private provider: Provider | undefined;
|
|
10
|
+
|
|
6
11
|
constructor(private outputDir: string) {
|
|
7
12
|
if (!fs.existsSync(this.outputDir)) {
|
|
8
13
|
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
@@ -10,8 +15,16 @@ export class GolangGenerator {
|
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
public generate(models: Model[]) {
|
|
13
|
-
//
|
|
14
|
-
|
|
18
|
+
// Remembered for the type mapping: `TIMESTAMP` is a rowversion on SQL Server and
|
|
19
|
+
// a point in time on every other provider.
|
|
20
|
+
this.provider = models[0]?.provider;
|
|
21
|
+
|
|
22
|
+
// 1. Generate per-model files like dotnet (<Model>.go) -
|
|
23
|
+
// entity struct + that model's WhereInput/OrderByInput/Args types.
|
|
24
|
+
// Same Go package, so no re-export shim is needed.
|
|
25
|
+
for (const model of models) {
|
|
26
|
+
this.generateModelFile(model);
|
|
27
|
+
}
|
|
15
28
|
|
|
16
29
|
// 2. Generate client.go - ORM DbContext + TableClient with full type-safe API
|
|
17
30
|
this.generateClientGo(models);
|
|
@@ -25,22 +38,28 @@ export class GolangGenerator {
|
|
|
25
38
|
|
|
26
39
|
// ─── Type mapping ──────────────────────────────────────────────────────────
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
goType = '
|
|
40
|
-
|
|
41
|
-
goType = '
|
|
42
|
-
|
|
43
|
-
goType = '
|
|
41
|
+
/**
|
|
42
|
+
* The Go type for a field.
|
|
43
|
+
*
|
|
44
|
+
* Driven by the declared type, not the generated TypeScript one: that collapses
|
|
45
|
+
* `INT`, `FLOAT` and `DECIMAL` into `number`, which matched none of the branches
|
|
46
|
+
* below and made every numeric column a `string`.
|
|
47
|
+
*/
|
|
48
|
+
private mapGoType(field: Field, isOptional: boolean): string {
|
|
49
|
+
const kind = fieldKind(field, this.provider);
|
|
50
|
+
let goType: string;
|
|
51
|
+
switch (kind) {
|
|
52
|
+
case 'int': goType = 'int'; break;
|
|
53
|
+
case 'bigint': goType = 'int64'; break;
|
|
54
|
+
case 'float': goType = 'float64'; break;
|
|
55
|
+
case 'bool': goType = 'bool'; break;
|
|
56
|
+
case 'date': goType = 'time.Time'; break;
|
|
57
|
+
case 'bytes': goType = '[]byte'; break;
|
|
58
|
+
case 'vector': goType = '[]float32'; break;
|
|
59
|
+
// A JSON column holds whatever the row put in it; string keeps any value
|
|
60
|
+
// representable, and json.RawMessage would need the import everywhere.
|
|
61
|
+
case 'json': goType = 'string'; break;
|
|
62
|
+
default: goType = 'string'; break;
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
if (isOptional && goType !== '[]byte') {
|
|
@@ -49,42 +68,56 @@ export class GolangGenerator {
|
|
|
49
68
|
return goType;
|
|
50
69
|
}
|
|
51
70
|
|
|
52
|
-
// ───
|
|
71
|
+
// ─── <Model>.go (per-model file, like dotnet `<Model>.cs`) ────────────────
|
|
53
72
|
|
|
54
|
-
private
|
|
73
|
+
private generateModelFile(model: Model) {
|
|
74
|
+
// Single-model file: entity struct + its WhereInput/OrderByInput/Args
|
|
75
|
+
// types. All files share `package an5client`, so callers keep using
|
|
76
|
+
// `an5client.User` etc. regardless of file layout.
|
|
77
|
+
// NOTE: Go rejects unused imports, so `time` is only imported when a
|
|
78
|
+
// field actually maps to `time.Time`.
|
|
79
|
+
const needsTime = model.fields.some(f => this.mapGoType(f, false).includes('time.Time'));
|
|
55
80
|
let content = `// Code generated by an5ORM. DO NOT EDIT.
|
|
56
81
|
package an5client
|
|
57
82
|
|
|
58
|
-
|
|
83
|
+
`;
|
|
84
|
+
if (needsTime) {
|
|
85
|
+
content += `import (
|
|
59
86
|
\t"time"
|
|
60
87
|
)
|
|
61
88
|
|
|
62
89
|
`;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
content += `type ${model.name} struct {\n`;
|
|
90
|
+
}
|
|
91
|
+
content += this.generateModelStruct(model);
|
|
92
|
+
content += this.generateModelOrmTypes(model);
|
|
68
93
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const jsonTag = field.isOptional ? `${this.toCamelCase(field.name)},omitempty` : this.toCamelCase(field.name);
|
|
72
|
-
const dbTag = this.toSnakeCase(field.name);
|
|
73
|
-
content += `\t${this.capitalize(field.name)} ${goType} \`json:"${jsonTag}" db:"${dbTag}"\`\n`;
|
|
74
|
-
}
|
|
94
|
+
fs.writeFileSync(path.join(this.outputDir, `${model.name}.go`), content);
|
|
95
|
+
}
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
97
|
+
private generateModelStruct(model: Model): string {
|
|
98
|
+
let content = '';
|
|
99
|
+
if (model.description) {
|
|
100
|
+
content += `// ${model.name} - ${model.description}\n`;
|
|
101
|
+
}
|
|
102
|
+
content += `type ${model.name} struct {\n`;
|
|
83
103
|
|
|
84
|
-
|
|
104
|
+
for (const field of model.fields) {
|
|
105
|
+
const goType = this.mapGoType(field, field.isOptional);
|
|
106
|
+
const jsonTag = field.isOptional ? `${this.toCamelCase(field.name)},omitempty` : this.toCamelCase(field.name);
|
|
107
|
+
const dbTag = this.toSnakeCase(field.name);
|
|
108
|
+
content += `\t${this.capitalize(field.name)} ${goType} \`json:"${jsonTag}" db:"${dbTag}"\`\n`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (model.relations.length > 0) {
|
|
112
|
+
content += `\n\t// Relations\n`;
|
|
113
|
+
for (const rel of model.relations) {
|
|
114
|
+
const relType = rel.isArray ? `[]${rel.type}` : `*${rel.type}`;
|
|
115
|
+
content += `\t${this.capitalize(rel.name)} ${relType} \`json:"${this.toCamelCase(rel.name)},omitempty"\`\n`;
|
|
116
|
+
}
|
|
85
117
|
}
|
|
86
118
|
|
|
87
|
-
|
|
119
|
+
content += `}\n\n`;
|
|
120
|
+
return content;
|
|
88
121
|
}
|
|
89
122
|
|
|
90
123
|
// ─── metadata.go ───────────────────────────────────────────────────────────
|
|
@@ -97,7 +130,7 @@ package an5client
|
|
|
97
130
|
var ModelToTable = map[string]string{
|
|
98
131
|
`;
|
|
99
132
|
for (const model of models) {
|
|
100
|
-
const fullTable =
|
|
133
|
+
const fullTable = bracketedTableName(model);
|
|
101
134
|
for (const prop of this.getAllPropertyVariations(model.name)) {
|
|
102
135
|
content += `\t"${prop}": "${fullTable}",\n`;
|
|
103
136
|
}
|
|
@@ -158,11 +191,7 @@ func GetDefaultConnectionString() string {
|
|
|
158
191
|
content += this.generateVectorSearch();
|
|
159
192
|
content += this.generateHelpers();
|
|
160
193
|
|
|
161
|
-
//
|
|
162
|
-
for (const model of models) {
|
|
163
|
-
content += this.generateModelOrmTypes(model);
|
|
164
|
-
}
|
|
165
|
-
|
|
194
|
+
// NOTE: per-model ORM types live in each `<Model>.go` file.
|
|
166
195
|
fs.writeFileSync(path.join(this.outputDir, 'client.go'), content);
|
|
167
196
|
}
|
|
168
197
|
|
|
@@ -267,7 +296,7 @@ func detectDialect(connStr string) Dialect {
|
|
|
267
296
|
\tif strings.HasPrefix(l, "postgres://") || strings.HasPrefix(l, "postgresql://") || strings.Contains(l, "port=5432") {
|
|
268
297
|
\t\treturn DialectPostgres
|
|
269
298
|
\t}
|
|
270
|
-
\tif strings.HasPrefix(l, "sqlite://") || strings.HasPrefix(l, "sqlite:") || strings.HasPrefix(l, "file:") || strings.HasSuffix(l, ".db") || strings.HasSuffix(l, ".sqlite") || strings.HasSuffix(l, ".sqlite3") || l == ":memory:" {
|
|
299
|
+
\tif l == "sqlite" || strings.HasPrefix(l, "sqlite://") || strings.HasPrefix(l, "sqlite:") || strings.HasPrefix(l, "file:") || strings.HasSuffix(l, ".db") || strings.HasSuffix(l, ".sqlite") || strings.HasSuffix(l, ".sqlite3") || l == ":memory:" {
|
|
271
300
|
\t\treturn DialectSqlite
|
|
272
301
|
\t}
|
|
273
302
|
\treturn DialectMssql
|
|
@@ -309,7 +338,7 @@ func NewAn5DbContextWithConnStr(db *sql.DB, connStr string) *An5DbContext {
|
|
|
309
338
|
`;
|
|
310
339
|
for (const model of models) {
|
|
311
340
|
const name = this.capitalize(model.name);
|
|
312
|
-
content += `\tclient${name} := NewTableClient[${model.name}](db, "${model
|
|
341
|
+
content += `\tclient${name} := NewTableClient[${model.name}](db, "${dottedTableName(model)}", d)\n`;
|
|
313
342
|
content += `\tctx.${name}s = client${name}\n`;
|
|
314
343
|
content += `\tctx.${name} = client${name}\n`;
|
|
315
344
|
}
|
|
@@ -348,6 +377,7 @@ func (c *TableClient[T]) quoteName(name string) string {
|
|
|
348
377
|
// quoteTable quotes a schema.table name for the dialect.
|
|
349
378
|
func (c *TableClient[T]) quoteTable() string {
|
|
350
379
|
\tparts := strings.Split(c.TableName, ".")
|
|
380
|
+
\tif c.Dialect == DialectSqlite && len(parts) == 2 && strings.EqualFold(parts[0], "dbo") { parts = parts[1:] }
|
|
351
381
|
\tquoted := make([]string, len(parts))
|
|
352
382
|
\tfor i, p := range parts {
|
|
353
383
|
\t\tquoted[i] = c.quoteName(p)
|
|
@@ -889,14 +919,14 @@ func (c *TableClient[T]) buildWhereFromStruct(v reflect.Value, argOffset int) (s
|
|
|
889
919
|
\t\t\tvar subParts []string
|
|
890
920
|
\t\t\tfor j := 0; j < fv.Len(); j++ {
|
|
891
921
|
\t\t\t\tsub, subArgs := c.buildWhereFromStruct(fv.Index(j), argOffset+len(args))
|
|
892
|
-
\t\t\t\tif sub
|
|
922
|
+
\t\t\t\tif sub == "" { sub = "1=1" }; subParts = append(subParts, sub); args = append(args, subArgs...)
|
|
893
923
|
\t\t\t}
|
|
894
|
-
\t\t\tif len(subParts) > 0 { parts = append(parts, "("+strings.Join(subParts, " OR ")+")") }
|
|
924
|
+
\t\t\tif len(subParts) > 0 { parts = append(parts, "("+strings.Join(subParts, " OR ")+")") } else { parts = append(parts, "1=0") }
|
|
895
925
|
\t\t\tcontinue
|
|
896
926
|
\t\t}
|
|
897
927
|
\t\tif name == "NOT" && fv.Kind() == reflect.Ptr && !fv.IsNil() {
|
|
898
928
|
\t\t\tsub, subArgs := c.buildWhereFromStruct(fv.Elem(), argOffset+len(args))
|
|
899
|
-
\t\t\tif sub
|
|
929
|
+
\t\t\tif sub == "" { sub = "1=1" }; parts = append(parts, "NOT ("+sub+")"); args = append(args, subArgs...)
|
|
900
930
|
\t\t\tcontinue
|
|
901
931
|
\t\t}
|
|
902
932
|
|
|
@@ -1136,7 +1166,7 @@ var _ = time.Time{}
|
|
|
1136
1166
|
content += `// ${name}CreateInput holds data for creating a new ${name} record.\ntype ${name}CreateInput struct {\n`;
|
|
1137
1167
|
for (const field of model.fields) {
|
|
1138
1168
|
if (!field.isId) {
|
|
1139
|
-
const goType = this.mapGoType(field
|
|
1169
|
+
const goType = this.mapGoType(field, field.isOptional || field.hasDefault);
|
|
1140
1170
|
content += `\t${this.capitalize(field.name)} ${goType}\n`;
|
|
1141
1171
|
}
|
|
1142
1172
|
}
|
|
@@ -1146,7 +1176,7 @@ var _ = time.Time{}
|
|
|
1146
1176
|
content += `// ${name}UpdateInput holds data for updating an existing ${name} record.\ntype ${name}UpdateInput struct {\n`;
|
|
1147
1177
|
for (const field of model.fields) {
|
|
1148
1178
|
if (!field.isId) {
|
|
1149
|
-
const goType = this.mapGoType(field
|
|
1179
|
+
const goType = this.mapGoType(field, true); // always pointer so we can detect zero
|
|
1150
1180
|
content += `\t${this.capitalize(field.name)} ${goType}\n`;
|
|
1151
1181
|
}
|
|
1152
1182
|
}
|
|
@@ -1187,20 +1217,16 @@ var _ = time.Time{}
|
|
|
1187
1217
|
}
|
|
1188
1218
|
|
|
1189
1219
|
private getGoFilterType(field: Field): string {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
return '
|
|
1199
|
-
}
|
|
1200
|
-
if (['bigint', 'long', 'float', 'real', 'double', 'decimal', 'numeric', 'money'].includes(lower)) {
|
|
1201
|
-
return 'NumberFilter';
|
|
1220
|
+
// From the declared type, so a DECIMAL column gets the number filter and an INT
|
|
1221
|
+
// column the integer one; both used to arrive as `number` and pick the first.
|
|
1222
|
+
switch (fieldKind(field, this.provider)) {
|
|
1223
|
+
case 'date': return 'DateTimeFilter';
|
|
1224
|
+
case 'bool': return 'BoolFilter';
|
|
1225
|
+
case 'int':
|
|
1226
|
+
case 'bigint': return 'IntFilter';
|
|
1227
|
+
case 'float': return 'NumberFilter';
|
|
1228
|
+
default: return 'StringFilter';
|
|
1202
1229
|
}
|
|
1203
|
-
return 'StringFilter';
|
|
1204
1230
|
}
|
|
1205
1231
|
|
|
1206
1232
|
// ─── Utilities ─────────────────────────────────────────────────────────────
|
package/generator/src/index.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { loadConfig, providerFromConfig, ConfigError, formatIssues } from './config';
|
|
3
|
+
import { FieldTypeError, PROVIDER_LABELS } from './field-types';
|
|
2
4
|
import { SchemaParser } from './parser';
|
|
3
5
|
import { CodeGenerator } from './code-generator';
|
|
4
6
|
import { MetadataGenerator } from './metadata-generator';
|
|
5
7
|
import { PythonGenerator } from './python-generator';
|
|
6
8
|
import { DotnetGenerator } from './dotnet-generator';
|
|
7
9
|
import { GolangGenerator } from './golang-generator';
|
|
10
|
+
import { RustGenerator } from './rust-generator';
|
|
8
11
|
|
|
9
12
|
import fs from 'fs';
|
|
10
13
|
|
|
@@ -24,52 +27,69 @@ function clearGeneratedFiles(outputDir: string, extension: string) {
|
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
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
|
+
|
|
27
48
|
async function main() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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>;
|
|
31
52
|
try {
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(configPath)) {
|
|
34
|
-
configPath = path.join(rootDir, 'an5Orm.config.cjs');
|
|
35
|
-
}
|
|
36
|
-
if (!fs.existsSync(configPath)) {
|
|
37
|
-
configPath = path.join(rootDir, '..', 'an5Orm.config.js');
|
|
38
|
-
}
|
|
39
|
-
if (!fs.existsSync(configPath)) {
|
|
40
|
-
configPath = path.join(rootDir, '..', 'an5Orm.config.cjs');
|
|
41
|
-
}
|
|
42
|
-
if (fs.existsSync(configPath)) {
|
|
43
|
-
config = require(configPath);
|
|
44
|
-
rootDir = path.dirname(configPath);
|
|
45
|
-
} else if (fs.existsSync(path.join(rootDir, '..', 'an5Schema'))) {
|
|
46
|
-
rootDir = path.resolve(rootDir, '..');
|
|
47
|
-
}
|
|
53
|
+
config = loadConfig();
|
|
48
54
|
} catch (err) {
|
|
55
|
+
if (err instanceof ConfigError) {
|
|
56
|
+
console.error(`❌ ${err.message}:`);
|
|
57
|
+
console.error(formatIssues(err.issues));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
49
60
|
console.warn('⚠️ Could not load an5Orm.config.js/.cjs, using defaults.', err);
|
|
61
|
+
config = loadConfig(path.join(process.cwd(), 'no-such-dir'));
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
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;
|
|
58
73
|
|
|
59
74
|
console.log('🚀 Starting ORM generation...');
|
|
60
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
|
+
|
|
61
81
|
try {
|
|
62
82
|
clearGeneratedFiles(outputTypesDir, '.ts');
|
|
63
83
|
clearGeneratedFiles(outputDotnetDir, '.cs');
|
|
64
84
|
clearGeneratedFiles(outputGolangDir, '.go');
|
|
65
|
-
|
|
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)) {
|
|
66
89
|
fs.unlinkSync(outputMetadataPath);
|
|
67
90
|
}
|
|
68
|
-
if (fs.existsSync(outputPythonMetadataPath)) {
|
|
69
|
-
fs.unlinkSync(outputPythonMetadataPath);
|
|
70
|
-
}
|
|
71
91
|
|
|
72
|
-
const parser = new SchemaParser(schemaDir);
|
|
92
|
+
const parser = new SchemaParser(schemaDir, provider);
|
|
73
93
|
const models = await parser.parse();
|
|
74
94
|
console.log(`📦 Parsed ${models.length} models from schema.`);
|
|
75
95
|
|
|
@@ -77,13 +97,17 @@ async function main() {
|
|
|
77
97
|
codeGen.generate(models);
|
|
78
98
|
console.log(`✨ Generated modular types in ${outputTypesDir}`);
|
|
79
99
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
fs.
|
|
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)');
|
|
83
110
|
}
|
|
84
|
-
const metadataGen = new MetadataGenerator(outputMetadataPath);
|
|
85
|
-
metadataGen.generate(models);
|
|
86
|
-
console.log(`✨ Generated metadata in ${outputMetadataPath}`);
|
|
87
111
|
|
|
88
112
|
const pythonDir = path.dirname(outputPythonMetadataPath);
|
|
89
113
|
if (!fs.existsSync(pythonDir)) {
|
|
@@ -101,8 +125,19 @@ async function main() {
|
|
|
101
125
|
golangGen.generate(models);
|
|
102
126
|
console.log(`✨ Generated Golang models in ${outputGolangDir}`);
|
|
103
127
|
|
|
128
|
+
const rustGen = new RustGenerator(outputRustDir);
|
|
129
|
+
rustGen.generate(models);
|
|
130
|
+
console.log(`✨ Generated Rust client in ${outputRustDir}`);
|
|
131
|
+
|
|
104
132
|
console.log('✅ ORM generation completed successfully.');
|
|
105
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
|
+
}
|
|
106
141
|
console.error('❌ ORM generation failed:', error);
|
|
107
142
|
process.exit(1);
|
|
108
143
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { Model } from './types';
|
|
3
|
+
import { Model, bracketedTableName } from './types';
|
|
4
4
|
|
|
5
5
|
export class MetadataGenerator {
|
|
6
6
|
constructor(
|
|
@@ -15,7 +15,7 @@ export class MetadataGenerator {
|
|
|
15
15
|
metaContent += 'export const modelToTable: Record<string, string> = {\n';
|
|
16
16
|
for (const model of models) {
|
|
17
17
|
const props = this.getAllPropertyVariations(model.name);
|
|
18
|
-
const fullTableName =
|
|
18
|
+
const fullTableName = bracketedTableName(model);
|
|
19
19
|
for (const prop of props) {
|
|
20
20
|
metaContent += ` ${prop}: "${fullTableName}",\n`;
|
|
21
21
|
}
|