@an5/orm 1.0.7 → 1.0.9
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 +97 -83
- package/dist/an5Metadata.d.ts +10 -0
- package/dist/an5Metadata.js +93 -0
- package/dist/an5Orm.d.ts +95 -3
- package/dist/an5Orm.js +808 -512
- package/dist/config.d.ts +2 -0
- package/dist/config.js +24 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +16 -0
- package/dist/generator/src/code-generator.d.ts +1 -0
- package/dist/generator/src/code-generator.js +44 -20
- package/dist/generator/src/dotnet-generator.d.ts +2 -0
- package/dist/generator/src/dotnet-generator.js +332 -6
- package/dist/generator/src/index.js +18 -2
- package/dist/generator/src/metadata-generator.d.ts +4 -1
- package/dist/generator/src/metadata-generator.js +22 -8
- package/dist/generator/src/python-generator.d.ts +10 -0
- package/dist/generator/src/python-generator.js +242 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +23 -8
- package/dist/logger.d.ts +53 -0
- package/dist/logger.js +117 -0
- package/dist/metadata.d.ts +24 -0
- package/dist/metadata.js +15 -0
- package/dist/migration-core.d.ts +87 -0
- package/dist/migration-core.js +692 -0
- package/dist/sql-utils.d.ts +31 -0
- package/dist/sql-utils.js +290 -0
- package/generator/src/code-generator.ts +45 -20
- package/generator/src/dotnet-generator.ts +344 -6
- package/generator/src/golang-generator.ts +1242 -0
- package/generator/src/index.ts +8 -1
- package/generator/src/metadata-generator.ts +29 -12
- package/generator/src/python-generator.ts +314 -4
- package/package.json +12 -10
- package/dist/cleanup.d.ts +0 -2
- package/dist/cleanup.js +0 -120
- package/dist/cli.js +0 -64
- package/dist/migrate.d.ts +0 -11
- package/dist/migrate.js +0 -301
- package/dist/pull.d.ts +0 -2
- package/dist/pull.js +0 -339
- package/dist/push.d.ts +0 -2
- package/dist/push.js +0 -281
- package/dist/seed.d.ts +0 -2
- package/dist/seed.js +0 -401
|
@@ -9,6 +9,7 @@ const code_generator_1 = require("./code-generator");
|
|
|
9
9
|
const metadata_generator_1 = require("./metadata-generator");
|
|
10
10
|
const python_generator_1 = require("./python-generator");
|
|
11
11
|
const dotnet_generator_1 = require("./dotnet-generator");
|
|
12
|
+
const golang_generator_1 = require("./golang-generator");
|
|
12
13
|
const fs_1 = __importDefault(require("fs"));
|
|
13
14
|
function clearGeneratedFiles(outputDir, extension) {
|
|
14
15
|
if (!fs_1.default.existsSync(outputDir)) {
|
|
@@ -26,15 +27,25 @@ function clearGeneratedFiles(outputDir, extension) {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
async function main() {
|
|
29
|
-
|
|
30
|
+
let rootDir = process.cwd();
|
|
30
31
|
let config = {};
|
|
31
32
|
try {
|
|
32
33
|
let configPath = path_1.default.join(rootDir, 'an5Orm.config.js');
|
|
33
34
|
if (!fs_1.default.existsSync(configPath)) {
|
|
34
35
|
configPath = path_1.default.join(rootDir, 'an5Orm.config.cjs');
|
|
35
36
|
}
|
|
37
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
38
|
+
configPath = path_1.default.join(rootDir, '..', 'an5Orm.config.js');
|
|
39
|
+
}
|
|
40
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
41
|
+
configPath = path_1.default.join(rootDir, '..', 'an5Orm.config.cjs');
|
|
42
|
+
}
|
|
36
43
|
if (fs_1.default.existsSync(configPath)) {
|
|
37
44
|
config = require(configPath);
|
|
45
|
+
rootDir = path_1.default.dirname(configPath);
|
|
46
|
+
}
|
|
47
|
+
else if (fs_1.default.existsSync(path_1.default.join(rootDir, '..', 'an5Schema'))) {
|
|
48
|
+
rootDir = path_1.default.resolve(rootDir, '..');
|
|
38
49
|
}
|
|
39
50
|
}
|
|
40
51
|
catch (err) {
|
|
@@ -45,10 +56,12 @@ async function main() {
|
|
|
45
56
|
const outputMetadataPath = path_1.default.resolve(rootDir, config.outputs?.typescript?.metadataFile || 'an5Client/typescript/an5Metadata.ts');
|
|
46
57
|
const outputPythonMetadataPath = path_1.default.resolve(rootDir, config.outputs?.python?.metadataFile || 'an5Client/python/an5_metadata.py');
|
|
47
58
|
const outputDotnetDir = path_1.default.resolve(rootDir, config.outputs?.dotnet?.outputDir || 'an5Client/dotnet');
|
|
59
|
+
const outputGolangDir = path_1.default.resolve(rootDir, config.outputs?.golang?.outputDir || 'an5Client/golang');
|
|
48
60
|
console.log('🚀 Starting ORM generation...');
|
|
49
61
|
try {
|
|
50
62
|
clearGeneratedFiles(outputTypesDir, '.ts');
|
|
51
63
|
clearGeneratedFiles(outputDotnetDir, '.cs');
|
|
64
|
+
clearGeneratedFiles(outputGolangDir, '.go');
|
|
52
65
|
if (fs_1.default.existsSync(outputMetadataPath)) {
|
|
53
66
|
fs_1.default.unlinkSync(outputMetadataPath);
|
|
54
67
|
}
|
|
@@ -74,10 +87,13 @@ async function main() {
|
|
|
74
87
|
}
|
|
75
88
|
const pythonGen = new python_generator_1.PythonGenerator(outputPythonMetadataPath);
|
|
76
89
|
pythonGen.generate(models);
|
|
77
|
-
console.log(`✨ Generated Python metadata in ${
|
|
90
|
+
console.log(`✨ Generated Python metadata and client models in ${pythonDir}`);
|
|
78
91
|
const dotnetGen = new dotnet_generator_1.DotnetGenerator(outputDotnetDir);
|
|
79
92
|
dotnetGen.generate(models);
|
|
80
93
|
console.log(`✨ Generated .NET models in ${outputDotnetDir}`);
|
|
94
|
+
const golangGen = new golang_generator_1.GolangGenerator(outputGolangDir);
|
|
95
|
+
golangGen.generate(models);
|
|
96
|
+
console.log(`✨ Generated Golang models in ${outputGolangDir}`);
|
|
81
97
|
console.log('✅ ORM generation completed successfully.');
|
|
82
98
|
}
|
|
83
99
|
catch (error) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Model } from './types';
|
|
2
2
|
export declare class MetadataGenerator {
|
|
3
3
|
private outputPath;
|
|
4
|
-
|
|
4
|
+
private relationImport;
|
|
5
|
+
constructor(outputPath: string, relationImport?: string);
|
|
5
6
|
generate(models: Model[]): void;
|
|
6
7
|
private getAllPropertyVariations;
|
|
8
|
+
private toSnakeCase;
|
|
9
|
+
private formatFieldMetadata;
|
|
7
10
|
private toCamelCase;
|
|
8
11
|
}
|
|
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.MetadataGenerator = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
class MetadataGenerator {
|
|
9
|
-
constructor(outputPath) {
|
|
9
|
+
constructor(outputPath, relationImport = 'import type { RelationDef } from "@an5/orm";') {
|
|
10
10
|
this.outputPath = outputPath;
|
|
11
|
+
this.relationImport = relationImport;
|
|
11
12
|
}
|
|
12
13
|
generate(models) {
|
|
13
14
|
let metaContent = '// This file is auto-generated. Do not edit directly.\n\n';
|
|
15
|
+
metaContent += `${this.relationImport}\n\n`;
|
|
14
16
|
metaContent += 'export const modelToTable: Record<string, string> = {\n';
|
|
15
17
|
for (const model of models) {
|
|
16
18
|
const props = this.getAllPropertyVariations(model.name);
|
|
@@ -37,7 +39,6 @@ class MetadataGenerator {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
metaContent += '};\n\n';
|
|
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';
|
|
42
43
|
for (const model of models) {
|
|
43
44
|
const props = this.getAllPropertyVariations(model.name);
|
|
@@ -55,22 +56,35 @@ class MetadataGenerator {
|
|
|
55
56
|
}
|
|
56
57
|
getAllPropertyVariations(modelName) {
|
|
57
58
|
const variations = new Set();
|
|
58
|
-
// 1.
|
|
59
|
+
// 1. PascalCase (User)
|
|
60
|
+
variations.add(modelName);
|
|
61
|
+
// 2. camelCase (user)
|
|
59
62
|
variations.add(this.toCamelCase(modelName));
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
+
// 3. Plural PascalCase (Users)
|
|
64
|
+
variations.add(modelName + 's');
|
|
65
|
+
// 4. Plural camelCase (users)
|
|
66
|
+
variations.add(this.toCamelCase(modelName) + 's');
|
|
67
|
+
// 5. snake_case (user, mc_server)
|
|
68
|
+
variations.add(this.toSnakeCase(modelName));
|
|
69
|
+
variations.add(this.toSnakeCase(modelName) + 's');
|
|
63
70
|
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
64
71
|
for (const acronym of acronyms) {
|
|
65
72
|
if (modelName.startsWith(acronym)) {
|
|
66
|
-
// e.g. LLMProvider -> llmProvider
|
|
67
73
|
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
|
|
68
|
-
// e.g. LLMProvider -> lLMProvider (Prisma style)
|
|
69
74
|
variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
|
|
75
|
+
variations.add(acronym.toLowerCase() + modelName.slice(acronym.length) + 's');
|
|
70
76
|
}
|
|
71
77
|
}
|
|
72
78
|
return Array.from(variations);
|
|
73
79
|
}
|
|
80
|
+
toSnakeCase(str) {
|
|
81
|
+
if (!str)
|
|
82
|
+
return '';
|
|
83
|
+
return str
|
|
84
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
85
|
+
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
86
|
+
.toLowerCase();
|
|
87
|
+
}
|
|
74
88
|
formatFieldMetadata(field) {
|
|
75
89
|
const entries = [
|
|
76
90
|
`ts: ${JSON.stringify(`${field.type}${field.isOptional ? '?' : ''}`)}`,
|
|
@@ -3,7 +3,17 @@ export declare class PythonGenerator {
|
|
|
3
3
|
private outputPath;
|
|
4
4
|
constructor(outputPath: string);
|
|
5
5
|
generate(models: Model[]): void;
|
|
6
|
+
private getPyFilterType;
|
|
7
|
+
private generateOrmTypes;
|
|
8
|
+
private generateMetadata;
|
|
9
|
+
private mapPyType;
|
|
10
|
+
private generateModels;
|
|
11
|
+
private generateClient;
|
|
12
|
+
private generateInit;
|
|
6
13
|
private getAllPropertyVariations;
|
|
14
|
+
private formatFields;
|
|
15
|
+
private pyString;
|
|
7
16
|
private toCamelCase;
|
|
8
17
|
private toSnakeCase;
|
|
18
|
+
private capitalize;
|
|
9
19
|
}
|
|
@@ -5,11 +5,111 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PythonGenerator = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
8
9
|
class PythonGenerator {
|
|
9
10
|
constructor(outputPath) {
|
|
10
11
|
this.outputPath = outputPath;
|
|
11
12
|
}
|
|
12
13
|
generate(models) {
|
|
14
|
+
const outputDir = path_1.default.dirname(this.outputPath);
|
|
15
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
16
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
// 1. Generate an5_metadata.py
|
|
19
|
+
this.generateMetadata(models);
|
|
20
|
+
// 2. Generate an5_models.py
|
|
21
|
+
this.generateModels(models, outputDir);
|
|
22
|
+
// 3. Generate an5_orm_types.py - type-safe ORM filter/args dataclasses
|
|
23
|
+
this.generateOrmTypes(models, outputDir);
|
|
24
|
+
// 4. Generate an5_client.py
|
|
25
|
+
this.generateClient(models, outputDir);
|
|
26
|
+
// 5. Generate __init__.py
|
|
27
|
+
this.generateInit(models, outputDir);
|
|
28
|
+
}
|
|
29
|
+
getPyFilterType(fieldType) {
|
|
30
|
+
const lower = fieldType.toLowerCase();
|
|
31
|
+
if (['datetime', 'datetime2', 'date', 'smalldatetime', 'datetimeoffset', 'timestamp'].includes(lower))
|
|
32
|
+
return 'DateTimeFilter';
|
|
33
|
+
if (['bool', 'boolean', 'bit'].includes(lower))
|
|
34
|
+
return 'BoolFilter';
|
|
35
|
+
if (['int', 'integer', 'smallint', 'tinyint', 'bigint', 'number'].includes(lower))
|
|
36
|
+
return 'IntFilter';
|
|
37
|
+
if (['float', 'real', 'double', 'decimal', 'numeric', 'money'].includes(lower))
|
|
38
|
+
return 'NumberFilter';
|
|
39
|
+
return 'StringFilter';
|
|
40
|
+
}
|
|
41
|
+
generateOrmTypes(models, outputDir) {
|
|
42
|
+
let content = '# This file is auto-generated. Do not edit directly.\n';
|
|
43
|
+
content += '"""\nAN5 ORM typed filter/args dataclasses for type-safe queries.\n\nUsage example::\n\n db.user.find_many(where=UserWhereInput(name=StringFilter(contains="John")),\n order_by=UserOrderByInput(created_at="desc"),\n take=10)\n"""\n';
|
|
44
|
+
content += 'from __future__ import annotations\n';
|
|
45
|
+
content += 'from dataclasses import dataclass, field\n';
|
|
46
|
+
content += 'from typing import List, Optional, Any\n';
|
|
47
|
+
content += 'from datetime import datetime\n\n';
|
|
48
|
+
// Base filter types
|
|
49
|
+
content += '# ─── Base filter types ────────────────────────────────────────────────────────\n\n';
|
|
50
|
+
content += `@dataclass\nclass StringFilter:\n """Type-safe filter for string fields."""\n equals: Optional[str] = None\n not_: Optional[str] = None\n contains: Optional[str] = None\n starts_with: Optional[str] = None\n ends_with: Optional[str] = None\n in_: Optional[List[str]] = None\n not_in: Optional[List[str]] = None\n gt: Optional[str] = None\n gte: Optional[str] = None\n lt: Optional[str] = None\n lte: Optional[str] = None\n\n`;
|
|
51
|
+
content += `@dataclass\nclass IntFilter:\n """Type-safe filter for integer fields."""\n equals: Optional[int] = None\n not_: Optional[int] = None\n in_: Optional[List[int]] = None\n not_in: Optional[List[int]] = None\n gt: Optional[int] = None\n gte: Optional[int] = None\n lt: Optional[int] = None\n lte: Optional[int] = None\n\n`;
|
|
52
|
+
content += `@dataclass\nclass NumberFilter:\n """Type-safe filter for float/decimal fields."""\n equals: Optional[float] = None\n not_: Optional[float] = None\n in_: Optional[List[float]] = None\n not_in: Optional[List[float]] = None\n gt: Optional[float] = None\n gte: Optional[float] = None\n lt: Optional[float] = None\n lte: Optional[float] = None\n\n`;
|
|
53
|
+
content += `@dataclass\nclass BoolFilter:\n """Type-safe filter for boolean fields."""\n equals: Optional[bool] = None\n\n`;
|
|
54
|
+
content += `@dataclass\nclass DateTimeFilter:\n """Type-safe filter for datetime fields."""\n equals: Optional[datetime] = None\n not_: Optional[datetime] = None\n in_: Optional[List[datetime]] = None\n not_in: Optional[List[datetime]] = None\n gt: Optional[datetime] = None\n gte: Optional[datetime] = None\n lt: Optional[datetime] = None\n lte: Optional[datetime] = None\n\n`;
|
|
55
|
+
// Per-model types
|
|
56
|
+
for (const model of models) {
|
|
57
|
+
const name = this.capitalize(model.name);
|
|
58
|
+
content += `# ─── ${name} ORM Types ────────────────────────────────────────────────────────\n\n`;
|
|
59
|
+
// WhereInput
|
|
60
|
+
content += `@dataclass\nclass ${name}WhereInput:\n """Type-safe WHERE filter for ${name} queries."""\n`;
|
|
61
|
+
content += ` AND: Optional[List['${name}WhereInput']] = None\n`;
|
|
62
|
+
content += ` OR: Optional[List['${name}WhereInput']] = None\n`;
|
|
63
|
+
content += ` NOT: Optional['${name}WhereInput'] = None\n`;
|
|
64
|
+
for (const f of model.fields) {
|
|
65
|
+
const ft = this.getPyFilterType(f.type);
|
|
66
|
+
content += ` ${this.toSnakeCase(f.name)}: Optional[${ft}] = None\n`;
|
|
67
|
+
}
|
|
68
|
+
content += '\n';
|
|
69
|
+
// OrderByInput
|
|
70
|
+
content += `@dataclass\nclass ${name}OrderByInput:\n """Type-safe ORDER BY for ${name} queries. Value: 'asc' or 'desc'."""\n`;
|
|
71
|
+
for (const f of model.fields) {
|
|
72
|
+
content += ` ${this.toSnakeCase(f.name)}: Optional[str] = None\n`;
|
|
73
|
+
}
|
|
74
|
+
content += '\n';
|
|
75
|
+
// CreateInput
|
|
76
|
+
const requiredFields = model.fields.filter(f => !f.isId && !f.isOptional && !f.hasDefault);
|
|
77
|
+
const optionalFields = model.fields.filter(f => !f.isId && (f.isOptional || f.hasDefault));
|
|
78
|
+
content += `@dataclass\nclass ${name}CreateInput:\n """Typed data for creating a new ${name} record."""\n`;
|
|
79
|
+
for (const f of requiredFields) {
|
|
80
|
+
content += ` ${this.toSnakeCase(f.name)}: ${this.mapPyType(f.type)}\n`;
|
|
81
|
+
}
|
|
82
|
+
for (const f of optionalFields) {
|
|
83
|
+
content += ` ${this.toSnakeCase(f.name)}: Optional[${this.mapPyType(f.type)}] = None\n`;
|
|
84
|
+
}
|
|
85
|
+
if (requiredFields.length === 0 && optionalFields.length === 0)
|
|
86
|
+
content += ' pass\n';
|
|
87
|
+
content += '\n';
|
|
88
|
+
// UpdateInput
|
|
89
|
+
const updateFields = model.fields.filter(f => !f.isId);
|
|
90
|
+
content += `@dataclass\nclass ${name}UpdateInput:\n """Typed data for updating an existing ${name} record."""\n`;
|
|
91
|
+
for (const f of updateFields) {
|
|
92
|
+
content += ` ${this.toSnakeCase(f.name)}: Optional[${this.mapPyType(f.type)}] = None\n`;
|
|
93
|
+
}
|
|
94
|
+
if (updateFields.length === 0)
|
|
95
|
+
content += ' pass\n';
|
|
96
|
+
content += '\n';
|
|
97
|
+
// FindManyArgs
|
|
98
|
+
content += `@dataclass\nclass ${name}FindManyArgs:\n """ORM-style args for ${name}.find_many()."""\n`;
|
|
99
|
+
content += ` where: Optional[${name}WhereInput] = None\n`;
|
|
100
|
+
content += ` order_by: Optional[${name}OrderByInput] = None\n`;
|
|
101
|
+
content += ` take: Optional[int] = None\n`;
|
|
102
|
+
content += ` skip: int = 0\n`;
|
|
103
|
+
content += ` select: Optional[List[str]] = None\n\n`;
|
|
104
|
+
// FindFirstArgs
|
|
105
|
+
content += `@dataclass\nclass ${name}FindFirstArgs:\n """ORM-style args for ${name}.find_first()."""\n`;
|
|
106
|
+
content += ` where: Optional[${name}WhereInput] = None\n`;
|
|
107
|
+
content += ` order_by: Optional[${name}OrderByInput] = None\n`;
|
|
108
|
+
content += ` select: Optional[List[str]] = None\n\n`;
|
|
109
|
+
}
|
|
110
|
+
fs_1.default.writeFileSync(path_1.default.join(outputDir, 'an5_orm_types.py'), content);
|
|
111
|
+
}
|
|
112
|
+
generateMetadata(models) {
|
|
13
113
|
let pyContent = '# This file is auto-generated. Do not edit directly.\n\n';
|
|
14
114
|
// 1. MODEL_TO_TABLE
|
|
15
115
|
pyContent += 'MODEL_TO_TABLE = {\n';
|
|
@@ -61,11 +161,146 @@ class PythonGenerator {
|
|
|
61
161
|
pyContent += '}\n';
|
|
62
162
|
fs_1.default.writeFileSync(this.outputPath, pyContent);
|
|
63
163
|
}
|
|
164
|
+
mapPyType(fieldType) {
|
|
165
|
+
const lower = fieldType.toLowerCase();
|
|
166
|
+
if (['int', 'integer', 'smallint', 'tinyint', 'bigint', 'number'].includes(lower)) {
|
|
167
|
+
return 'int';
|
|
168
|
+
}
|
|
169
|
+
if (['float', 'real', 'double', 'decimal', 'numeric', 'money'].includes(lower)) {
|
|
170
|
+
return 'float';
|
|
171
|
+
}
|
|
172
|
+
if (['bool', 'boolean', 'bit'].includes(lower)) {
|
|
173
|
+
return 'bool';
|
|
174
|
+
}
|
|
175
|
+
if (['datetime', 'datetime2', 'date', 'smalldatetime', 'datetimeoffset', 'timestamp'].includes(lower)) {
|
|
176
|
+
return 'datetime';
|
|
177
|
+
}
|
|
178
|
+
if (['bytes', 'binary', 'varbinary'].includes(lower)) {
|
|
179
|
+
return 'bytes';
|
|
180
|
+
}
|
|
181
|
+
return 'str';
|
|
182
|
+
}
|
|
183
|
+
generateModels(models, outputDir) {
|
|
184
|
+
let content = '# This file is auto-generated. Do not edit directly.\n';
|
|
185
|
+
content += 'from dataclasses import dataclass, field\n';
|
|
186
|
+
content += 'from typing import Optional, List, Any\n';
|
|
187
|
+
content += 'from datetime import datetime\n\n';
|
|
188
|
+
for (const model of models) {
|
|
189
|
+
if (model.description) {
|
|
190
|
+
content += `"""${model.description}"""\n`;
|
|
191
|
+
}
|
|
192
|
+
content += `@dataclass\nclass ${model.name}:\n`;
|
|
193
|
+
if (model.fields.length === 0 && model.relations.length === 0) {
|
|
194
|
+
content += ' pass\n\n';
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Required fields first
|
|
198
|
+
const required = model.fields.filter(f => !f.isOptional && !f.hasDefault);
|
|
199
|
+
const optionals = model.fields.filter(f => f.isOptional || f.hasDefault);
|
|
200
|
+
for (const f of required) {
|
|
201
|
+
const pyType = this.mapPyType(f.type);
|
|
202
|
+
const snakeName = this.toSnakeCase(f.name);
|
|
203
|
+
content += ` ${snakeName}: ${pyType}\n`;
|
|
204
|
+
}
|
|
205
|
+
for (const f of optionals) {
|
|
206
|
+
const pyType = this.mapPyType(f.type);
|
|
207
|
+
const snakeName = this.toSnakeCase(f.name);
|
|
208
|
+
content += ` ${snakeName}: Optional[${pyType}] = None\n`;
|
|
209
|
+
}
|
|
210
|
+
// Relations
|
|
211
|
+
for (const rel of model.relations) {
|
|
212
|
+
const relName = this.toSnakeCase(rel.name);
|
|
213
|
+
if (rel.isArray) {
|
|
214
|
+
content += ` ${relName}: List[Any] = field(default_factory=list)\n`;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
content += ` ${relName}: Optional[Any] = None\n`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
content += '\n';
|
|
221
|
+
}
|
|
222
|
+
fs_1.default.writeFileSync(path_1.default.join(outputDir, 'an5_models.py'), content);
|
|
223
|
+
}
|
|
224
|
+
generateClient(models, outputDir) {
|
|
225
|
+
let content = '# This file is auto-generated. Do not edit directly.\n';
|
|
226
|
+
content += 'import os\n';
|
|
227
|
+
content += 'from typing import Dict, List, Optional, Any, Callable\n\n';
|
|
228
|
+
content += 'try:\n';
|
|
229
|
+
content += ' from an5_adapter import An5Adapter, AdapterTableClient, create_an5_adapter, set_adapter_metadata\n';
|
|
230
|
+
content += 'except ImportError:\n';
|
|
231
|
+
content += ' from .an5_adapter import An5Adapter, AdapterTableClient, create_an5_adapter, set_adapter_metadata\n\n';
|
|
232
|
+
content += 'try:\n';
|
|
233
|
+
content += ' from .an5_metadata import MODEL_TO_TABLE, MODEL_FIELDS\n';
|
|
234
|
+
content += 'except ImportError:\n';
|
|
235
|
+
content += ' from an5_metadata import MODEL_TO_TABLE, MODEL_FIELDS\n\n';
|
|
236
|
+
content += 'class An5Client:\n';
|
|
237
|
+
content += ' """AN5 Python ORM Client - type-safe database access.\n\n';
|
|
238
|
+
content += ' Usage:\n';
|
|
239
|
+
content += ' db = An5Client()\n';
|
|
240
|
+
content += ' users = db.user.find_many(where={"name": {"contains": "John"}}, order_by={"created_at": "asc"}, take=10)\n';
|
|
241
|
+
content += ' user = db.user.find_first(where={"id": "abc"})\n';
|
|
242
|
+
content += ' new = db.user.create(data={"name": "Alice", "email": "alice@example.com"})\n';
|
|
243
|
+
content += ' """\n';
|
|
244
|
+
content += ' def __init__(self, connection_string: Optional[str] = None):\n';
|
|
245
|
+
content += ' conn_str = (connection_string or \n';
|
|
246
|
+
content += ' os.getenv("DATABASE_URL") or \n';
|
|
247
|
+
content += ' "Server=localhost;Database=master;Trusted_Connection=True;TrustServerCertificate=True;")\n';
|
|
248
|
+
content += ' set_adapter_metadata({"model_to_table": MODEL_TO_TABLE, "model_fields": MODEL_FIELDS})\n';
|
|
249
|
+
content += ' self.adapter: An5Adapter = create_an5_adapter(conn_str)\n\n';
|
|
250
|
+
for (const model of models) {
|
|
251
|
+
const propName = this.toSnakeCase(model.name) + 's';
|
|
252
|
+
const singleName = this.toSnakeCase(model.name);
|
|
253
|
+
content += ` client = AdapterTableClient(self.adapter, "${model.name}")\n`;
|
|
254
|
+
content += ` self.${model.name}: AdapterTableClient = client\n`;
|
|
255
|
+
content += ` self.${model.name}s: AdapterTableClient = client\n`;
|
|
256
|
+
content += ` self.${singleName}: AdapterTableClient = client\n`;
|
|
257
|
+
content += ` self.${propName}: AdapterTableClient = client\n`;
|
|
258
|
+
}
|
|
259
|
+
content += '\n';
|
|
260
|
+
content += ' def __getattr__(self, name: str) -> AdapterTableClient:\n';
|
|
261
|
+
content += ' return self.adapter.table(name)\n\n';
|
|
262
|
+
content += ' def query_raw(self, sql: str, *params) -> List[Dict]:\n';
|
|
263
|
+
content += ' return self.adapter.query_raw(sql, *params)\n\n';
|
|
264
|
+
content += ' def execute_raw(self, sql: str, *params) -> int:\n';
|
|
265
|
+
content += ' return self.adapter.execute_raw(sql, *params)\n\n';
|
|
266
|
+
content += ' def transaction(self, fn: Callable) -> Any:\n';
|
|
267
|
+
content += ' return self.adapter.transaction(fn)\n';
|
|
268
|
+
fs_1.default.writeFileSync(path_1.default.join(outputDir, 'an5_client.py'), content);
|
|
269
|
+
}
|
|
270
|
+
generateInit(models, outputDir) {
|
|
271
|
+
let content = '# This file is auto-generated. Do not edit directly.\n';
|
|
272
|
+
content += 'from .an5_metadata import MODEL_TO_TABLE, MODEL_DESCRIPTIONS, MODEL_FIELDS, RELATION_MAP\n';
|
|
273
|
+
content += 'from .an5_models import *\n';
|
|
274
|
+
content += 'from .an5_orm_types import (\n';
|
|
275
|
+
content += ' StringFilter, IntFilter, NumberFilter, BoolFilter, DateTimeFilter,\n';
|
|
276
|
+
for (const m of models) {
|
|
277
|
+
const name = this.capitalize(m.name);
|
|
278
|
+
content += ` ${name}WhereInput, ${name}OrderByInput,\n`;
|
|
279
|
+
content += ` ${name}CreateInput, ${name}UpdateInput,\n`;
|
|
280
|
+
content += ` ${name}FindManyArgs, ${name}FindFirstArgs,\n`;
|
|
281
|
+
}
|
|
282
|
+
content += ')\n';
|
|
283
|
+
content += 'from .an5_client import An5Client\n\n';
|
|
284
|
+
content += '__all__ = [\n';
|
|
285
|
+
content += ' "MODEL_TO_TABLE",\n';
|
|
286
|
+
content += ' "MODEL_DESCRIPTIONS",\n';
|
|
287
|
+
content += ' "MODEL_FIELDS",\n';
|
|
288
|
+
content += ' "RELATION_MAP",\n';
|
|
289
|
+
content += ' "An5Client",\n';
|
|
290
|
+
content += ' "StringFilter", "IntFilter", "NumberFilter", "BoolFilter", "DateTimeFilter",\n';
|
|
291
|
+
for (const m of models) {
|
|
292
|
+
const name = this.capitalize(m.name);
|
|
293
|
+
content += ` "${m.name}",\n`;
|
|
294
|
+
content += ` "${name}WhereInput", "${name}OrderByInput",\n`;
|
|
295
|
+
content += ` "${name}CreateInput", "${name}UpdateInput",\n`;
|
|
296
|
+
content += ` "${name}FindManyArgs", "${name}FindFirstArgs",\n`;
|
|
297
|
+
}
|
|
298
|
+
content += ']\n';
|
|
299
|
+
fs_1.default.writeFileSync(path_1.default.join(outputDir, '__init__.py'), content);
|
|
300
|
+
}
|
|
64
301
|
getAllPropertyVariations(modelName) {
|
|
65
302
|
const variations = new Set();
|
|
66
|
-
// 1. camelCase (User -> user, McpServer -> mcpServer)
|
|
67
303
|
variations.add(this.toCamelCase(modelName));
|
|
68
|
-
// 2. snake_case (User -> user, McpServer -> mcp_server)
|
|
69
304
|
variations.add(this.toSnakeCase(modelName));
|
|
70
305
|
const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
|
|
71
306
|
for (const acronym of acronyms) {
|
|
@@ -101,5 +336,10 @@ class PythonGenerator {
|
|
|
101
336
|
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
|
102
337
|
.toLowerCase();
|
|
103
338
|
}
|
|
339
|
+
capitalize(str) {
|
|
340
|
+
if (!str)
|
|
341
|
+
return '';
|
|
342
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
343
|
+
}
|
|
104
344
|
}
|
|
105
345
|
exports.PythonGenerator = PythonGenerator;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
1
|
+
export { An5ClientKnownRequestError } from "./errors";
|
|
2
|
+
export { DEFAULT_METADATA } from "./metadata";
|
|
3
|
+
export type { RelationDef, ModelFieldMeta, An5Metadata } from "./metadata";
|
|
4
|
+
export { quoteIdentifier, quoteTableIdentifier, sanitizeParamName, normalizeSortDirection, toNonNegativeInt, buildOrderBy, parseWhere, } from "./sql-utils";
|
|
5
|
+
export { generateDiff, buildMigrationFile, buildCreateTableSql, generateColumnDiff, parseSchemaText, } from "./migration-core";
|
|
6
|
+
export { An5Adapter, createAn5Adapter, AdapterTableClient, } from "@an5/adapters";
|
|
3
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
var
|
|
8
|
-
Object.defineProperty(exports, "
|
|
9
|
-
|
|
10
|
-
Object.defineProperty(exports, "
|
|
3
|
+
exports.AdapterTableClient = exports.createAn5Adapter = exports.An5Adapter = exports.parseSchemaText = exports.generateColumnDiff = exports.buildCreateTableSql = exports.buildMigrationFile = exports.generateDiff = exports.parseWhere = exports.buildOrderBy = exports.toNonNegativeInt = exports.normalizeSortDirection = exports.sanitizeParamName = exports.quoteTableIdentifier = exports.quoteIdentifier = exports.DEFAULT_METADATA = exports.An5ClientKnownRequestError = void 0;
|
|
4
|
+
var errors_1 = require("./errors");
|
|
5
|
+
Object.defineProperty(exports, "An5ClientKnownRequestError", { enumerable: true, get: function () { return errors_1.An5ClientKnownRequestError; } });
|
|
6
|
+
var metadata_1 = require("./metadata");
|
|
7
|
+
Object.defineProperty(exports, "DEFAULT_METADATA", { enumerable: true, get: function () { return metadata_1.DEFAULT_METADATA; } });
|
|
8
|
+
var sql_utils_1 = require("./sql-utils");
|
|
9
|
+
Object.defineProperty(exports, "quoteIdentifier", { enumerable: true, get: function () { return sql_utils_1.quoteIdentifier; } });
|
|
10
|
+
Object.defineProperty(exports, "quoteTableIdentifier", { enumerable: true, get: function () { return sql_utils_1.quoteTableIdentifier; } });
|
|
11
|
+
Object.defineProperty(exports, "sanitizeParamName", { enumerable: true, get: function () { return sql_utils_1.sanitizeParamName; } });
|
|
12
|
+
Object.defineProperty(exports, "normalizeSortDirection", { enumerable: true, get: function () { return sql_utils_1.normalizeSortDirection; } });
|
|
13
|
+
Object.defineProperty(exports, "toNonNegativeInt", { enumerable: true, get: function () { return sql_utils_1.toNonNegativeInt; } });
|
|
14
|
+
Object.defineProperty(exports, "buildOrderBy", { enumerable: true, get: function () { return sql_utils_1.buildOrderBy; } });
|
|
15
|
+
Object.defineProperty(exports, "parseWhere", { enumerable: true, get: function () { return sql_utils_1.parseWhere; } });
|
|
16
|
+
var migration_core_1 = require("./migration-core");
|
|
17
|
+
Object.defineProperty(exports, "generateDiff", { enumerable: true, get: function () { return migration_core_1.generateDiff; } });
|
|
18
|
+
Object.defineProperty(exports, "buildMigrationFile", { enumerable: true, get: function () { return migration_core_1.buildMigrationFile; } });
|
|
19
|
+
Object.defineProperty(exports, "buildCreateTableSql", { enumerable: true, get: function () { return migration_core_1.buildCreateTableSql; } });
|
|
20
|
+
Object.defineProperty(exports, "generateColumnDiff", { enumerable: true, get: function () { return migration_core_1.generateColumnDiff; } });
|
|
21
|
+
Object.defineProperty(exports, "parseSchemaText", { enumerable: true, get: function () { return migration_core_1.parseSchemaText; } });
|
|
22
|
+
var adapters_1 = require("@an5/adapters");
|
|
23
|
+
Object.defineProperty(exports, "An5Adapter", { enumerable: true, get: function () { return adapters_1.An5Adapter; } });
|
|
24
|
+
Object.defineProperty(exports, "createAn5Adapter", { enumerable: true, get: function () { return adapters_1.createAn5Adapter; } });
|
|
25
|
+
Object.defineProperty(exports, "AdapterTableClient", { enumerable: true, get: function () { return adapters_1.AdapterTableClient; } });
|
|
11
26
|
//# sourceMappingURL=index.js.map
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured Logger for an5Orm
|
|
3
|
+
* Provides consistent logging with levels, timestamps, and context.
|
|
4
|
+
*/
|
|
5
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
6
|
+
export interface LogEntry {
|
|
7
|
+
level: LogLevel;
|
|
8
|
+
message: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
context?: string;
|
|
11
|
+
data?: any;
|
|
12
|
+
duration?: number;
|
|
13
|
+
error?: Error;
|
|
14
|
+
}
|
|
15
|
+
export interface LoggerOptions {
|
|
16
|
+
level?: LogLevel;
|
|
17
|
+
prefix?: string;
|
|
18
|
+
json?: boolean;
|
|
19
|
+
colors?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare class Logger {
|
|
22
|
+
private level;
|
|
23
|
+
private prefix;
|
|
24
|
+
private json;
|
|
25
|
+
private colors;
|
|
26
|
+
constructor(options?: LoggerOptions);
|
|
27
|
+
private formatEntry;
|
|
28
|
+
private log;
|
|
29
|
+
debug(message: string, options?: {
|
|
30
|
+
context?: string;
|
|
31
|
+
data?: any;
|
|
32
|
+
}): void;
|
|
33
|
+
info(message: string, options?: {
|
|
34
|
+
context?: string;
|
|
35
|
+
data?: any;
|
|
36
|
+
duration?: number;
|
|
37
|
+
}): void;
|
|
38
|
+
warn(message: string, options?: {
|
|
39
|
+
context?: string;
|
|
40
|
+
data?: any;
|
|
41
|
+
}): void;
|
|
42
|
+
error(message: string, error?: Error, options?: {
|
|
43
|
+
context?: string;
|
|
44
|
+
data?: any;
|
|
45
|
+
}): void;
|
|
46
|
+
child(context: string): Logger;
|
|
47
|
+
timer(label: string): {
|
|
48
|
+
end: () => void;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export declare const logger: Logger;
|
|
52
|
+
export declare function createLogger(context: string): Logger;
|
|
53
|
+
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Structured Logger for an5Orm
|
|
4
|
+
* Provides consistent logging with levels, timestamps, and context.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.logger = exports.Logger = void 0;
|
|
8
|
+
exports.createLogger = createLogger;
|
|
9
|
+
const LOG_LEVELS = {
|
|
10
|
+
debug: 0,
|
|
11
|
+
info: 1,
|
|
12
|
+
warn: 2,
|
|
13
|
+
error: 3,
|
|
14
|
+
};
|
|
15
|
+
const COLORS = {
|
|
16
|
+
debug: '\x1b[36m', // cyan
|
|
17
|
+
info: '\x1b[32m', // green
|
|
18
|
+
warn: '\x1b[33m', // yellow
|
|
19
|
+
error: '\x1b[31m', // red
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
};
|
|
22
|
+
class Logger {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.level = LOG_LEVELS[options.level || 'info'];
|
|
25
|
+
this.prefix = options.prefix || '';
|
|
26
|
+
this.json = options.json || false;
|
|
27
|
+
this.colors = options.colors !== false && !this.json;
|
|
28
|
+
}
|
|
29
|
+
formatEntry(entry) {
|
|
30
|
+
if (this.json) {
|
|
31
|
+
return JSON.stringify({
|
|
32
|
+
level: entry.level,
|
|
33
|
+
message: entry.message,
|
|
34
|
+
timestamp: entry.timestamp,
|
|
35
|
+
context: entry.context,
|
|
36
|
+
duration: entry.duration,
|
|
37
|
+
...(entry.data && { data: entry.data }),
|
|
38
|
+
...(entry.error && { error: entry.error.message, stack: entry.error.stack }),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const color = this.colors ? COLORS[entry.level] : '';
|
|
42
|
+
const reset = this.colors ? COLORS.reset : '';
|
|
43
|
+
const prefix = this.prefix ? `[${this.prefix}]` : '';
|
|
44
|
+
const context = entry.context ? `[${entry.context}]` : '';
|
|
45
|
+
const duration = entry.duration !== undefined ? ` (${entry.duration}ms)` : '';
|
|
46
|
+
const timestamp = entry.timestamp;
|
|
47
|
+
let line = `${color}${timestamp} ${entry.level.toUpperCase().padEnd(5)}${reset} ${prefix}${context} ${entry.message}${duration}`;
|
|
48
|
+
if (entry.error) {
|
|
49
|
+
line += `\n${color}${entry.error.stack || entry.error.message}${reset}`;
|
|
50
|
+
}
|
|
51
|
+
return line;
|
|
52
|
+
}
|
|
53
|
+
log(level, message, options) {
|
|
54
|
+
if (LOG_LEVELS[level] < this.level)
|
|
55
|
+
return;
|
|
56
|
+
const entry = {
|
|
57
|
+
level,
|
|
58
|
+
message,
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
...options,
|
|
61
|
+
};
|
|
62
|
+
const formatted = this.formatEntry(entry);
|
|
63
|
+
switch (level) {
|
|
64
|
+
case 'error':
|
|
65
|
+
console.error(formatted);
|
|
66
|
+
break;
|
|
67
|
+
case 'warn':
|
|
68
|
+
console.warn(formatted);
|
|
69
|
+
break;
|
|
70
|
+
case 'debug':
|
|
71
|
+
console.debug(formatted);
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
console.log(formatted);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
debug(message, options) {
|
|
78
|
+
this.log('debug', message, options);
|
|
79
|
+
}
|
|
80
|
+
info(message, options) {
|
|
81
|
+
this.log('info', message, options);
|
|
82
|
+
}
|
|
83
|
+
warn(message, options) {
|
|
84
|
+
this.log('warn', message, options);
|
|
85
|
+
}
|
|
86
|
+
error(message, error, options) {
|
|
87
|
+
this.log('error', message, { ...options, ...(error !== undefined ? { error } : {}) });
|
|
88
|
+
}
|
|
89
|
+
child(context) {
|
|
90
|
+
return new Logger({
|
|
91
|
+
level: Object.keys(LOG_LEVELS).find(k => LOG_LEVELS[k] === this.level),
|
|
92
|
+
prefix: this.prefix ? `${this.prefix}:${context}` : context,
|
|
93
|
+
json: this.json,
|
|
94
|
+
colors: this.colors,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
timer(label) {
|
|
98
|
+
const start = Date.now();
|
|
99
|
+
return {
|
|
100
|
+
end: () => {
|
|
101
|
+
const duration = Date.now() - start;
|
|
102
|
+
this.info(`${label} completed`, { duration });
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.Logger = Logger;
|
|
108
|
+
// Default logger instance
|
|
109
|
+
exports.logger = new Logger({
|
|
110
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
111
|
+
prefix: 'an5Orm',
|
|
112
|
+
});
|
|
113
|
+
// Create scoped loggers
|
|
114
|
+
function createLogger(context) {
|
|
115
|
+
return exports.logger.child(context);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=logger.js.map
|