@an5/orm 1.0.8 → 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.
Files changed (38) hide show
  1. package/README.md +167 -175
  2. package/dist/an5Metadata.js +64 -0
  3. package/dist/an5Orm.d.ts +92 -3
  4. package/dist/an5Orm.js +697 -206
  5. package/dist/errors.d.ts +1 -1
  6. package/dist/errors.js +1 -1
  7. package/dist/generator/src/code-generator.d.ts +14 -13
  8. package/dist/generator/src/code-generator.js +249 -225
  9. package/dist/generator/src/dotnet-generator.d.ts +13 -11
  10. package/dist/generator/src/dotnet-generator.js +872 -546
  11. package/dist/generator/src/index.d.ts +1 -1
  12. package/dist/generator/src/index.js +104 -88
  13. package/dist/generator/src/metadata-generator.d.ts +11 -8
  14. package/dist/generator/src/metadata-generator.js +104 -90
  15. package/dist/generator/src/parser.d.ts +11 -11
  16. package/dist/generator/src/parser.js +206 -206
  17. package/dist/generator/src/python-generator.d.ts +19 -9
  18. package/dist/generator/src/python-generator.js +345 -105
  19. package/dist/generator/src/types.d.ts +27 -27
  20. package/dist/generator/src/types.js +2 -2
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.js +19 -8
  23. package/dist/logger.js +1 -1
  24. package/dist/migration-core.d.ts +87 -0
  25. package/dist/migration-core.js +692 -0
  26. package/dist/scripts/release-cli.d.ts +2 -2
  27. package/dist/scripts/release-cli.js +115 -115
  28. package/dist/sql-utils.d.ts +3 -1
  29. package/dist/sql-utils.js +94 -24
  30. package/generator/src/code-generator.ts +259 -230
  31. package/generator/src/dotnet-generator.ts +882 -544
  32. package/generator/src/golang-generator.ts +1242 -0
  33. package/generator/src/index.ts +111 -116
  34. package/generator/src/metadata-generator.ts +116 -101
  35. package/generator/src/parser.ts +202 -202
  36. package/generator/src/python-generator.ts +424 -114
  37. package/generator/src/types.ts +29 -29
  38. package/package.json +12 -6
@@ -1,105 +1,345 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PythonGenerator = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- class PythonGenerator {
9
- constructor(outputPath) {
10
- this.outputPath = outputPath;
11
- }
12
- generate(models) {
13
- let pyContent = '# This file is auto-generated. Do not edit directly.\n\n';
14
- // 1. MODEL_TO_TABLE
15
- pyContent += 'MODEL_TO_TABLE = {\n';
16
- for (const model of models) {
17
- const props = this.getAllPropertyVariations(model.name);
18
- const fullTableName = `[${model.schemaName}].[${model.tableName}]`;
19
- for (const prop of props) {
20
- pyContent += ` "${prop}": "${fullTableName}",\n`;
21
- }
22
- }
23
- pyContent += '}\n\n';
24
- // 2. MODEL_DESCRIPTIONS
25
- pyContent += 'MODEL_DESCRIPTIONS = {\n';
26
- for (const model of models) {
27
- const props = this.getAllPropertyVariations(model.name);
28
- for (const prop of props) {
29
- pyContent += ` "${prop}": ${this.pyString(model.description)},\n`;
30
- }
31
- }
32
- pyContent += '}\n\n';
33
- // 3. MODEL_FIELDS
34
- pyContent += 'MODEL_FIELDS = {\n';
35
- for (const model of models) {
36
- const props = this.getAllPropertyVariations(model.name);
37
- const fieldsStr = this.formatFields(model);
38
- for (const prop of props) {
39
- pyContent += ` "${prop}": ${fieldsStr},\n`;
40
- }
41
- }
42
- pyContent += '}\n\n';
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
- for (const prop of props) {
58
- pyContent += ` "${prop}": ${relationsContent},\n`;
59
- }
60
- }
61
- pyContent += '}\n';
62
- fs_1.default.writeFileSync(this.outputPath, pyContent);
63
- }
64
- getAllPropertyVariations(modelName) {
65
- const variations = new Set();
66
- // 1. camelCase (User -> user, McpServer -> mcpServer)
67
- variations.add(this.toCamelCase(modelName));
68
- // 2. snake_case (User -> user, McpServer -> mcp_server)
69
- variations.add(this.toSnakeCase(modelName));
70
- const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
71
- for (const acronym of acronyms) {
72
- if (modelName.startsWith(acronym)) {
73
- variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
74
- variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
75
- variations.add(this.toSnakeCase(acronym.toLowerCase() + modelName.slice(acronym.length)));
76
- }
77
- }
78
- return Array.from(variations);
79
- }
80
- formatFields(model) {
81
- if (model.fields.length === 0)
82
- return '[]';
83
- const fields = model.fields.map(f => {
84
- return ` { "name": "${f.name}", "type": "${f.type}${f.isOptional ? '?' : ''}", "sql": "${f.sqlType}", "isOptional": ${f.isOptional ? 'True' : 'False'}, "hasDefault": ${f.hasDefault ? 'True' : 'False'}, "isId": ${f.isId ? 'True' : 'False'}, "description": ${this.pyString(f.description)} }`;
85
- });
86
- return `[\n${fields.join(',\n')}\n ]`;
87
- }
88
- pyString(value) {
89
- return value ? JSON.stringify(value) : 'None';
90
- }
91
- toCamelCase(str) {
92
- if (!str)
93
- return '';
94
- return str[0].toLowerCase() + str.slice(1);
95
- }
96
- toSnakeCase(str) {
97
- if (!str)
98
- return '';
99
- return str
100
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
101
- .replace(/([a-z\d])([A-Z])/g, '$1_$2')
102
- .toLowerCase();
103
- }
104
- }
105
- exports.PythonGenerator = PythonGenerator;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PythonGenerator = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class PythonGenerator {
10
+ constructor(outputPath) {
11
+ this.outputPath = outputPath;
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) {
113
+ let pyContent = '# This file is auto-generated. Do not edit directly.\n\n';
114
+ // 1. MODEL_TO_TABLE
115
+ pyContent += 'MODEL_TO_TABLE = {\n';
116
+ for (const model of models) {
117
+ const props = this.getAllPropertyVariations(model.name);
118
+ const fullTableName = `[${model.schemaName}].[${model.tableName}]`;
119
+ for (const prop of props) {
120
+ pyContent += ` "${prop}": "${fullTableName}",\n`;
121
+ }
122
+ }
123
+ pyContent += '}\n\n';
124
+ // 2. MODEL_DESCRIPTIONS
125
+ pyContent += 'MODEL_DESCRIPTIONS = {\n';
126
+ for (const model of models) {
127
+ const props = this.getAllPropertyVariations(model.name);
128
+ for (const prop of props) {
129
+ pyContent += ` "${prop}": ${this.pyString(model.description)},\n`;
130
+ }
131
+ }
132
+ pyContent += '}\n\n';
133
+ // 3. MODEL_FIELDS
134
+ pyContent += 'MODEL_FIELDS = {\n';
135
+ for (const model of models) {
136
+ const props = this.getAllPropertyVariations(model.name);
137
+ const fieldsStr = this.formatFields(model);
138
+ for (const prop of props) {
139
+ pyContent += ` "${prop}": ${fieldsStr},\n`;
140
+ }
141
+ }
142
+ pyContent += '}\n\n';
143
+ // 4. RELATION_MAP
144
+ pyContent += 'RELATION_MAP = {\n';
145
+ for (const model of models) {
146
+ const props = this.getAllPropertyVariations(model.name);
147
+ let relationsContent = `{\n`;
148
+ for (const rel of model.relations) {
149
+ relationsContent += ` "${rel.name}": {\n`;
150
+ relationsContent += ` "modelName": "${this.toCamelCase(rel.type)}",\n`;
151
+ relationsContent += ` "relationType": "${rel.isArray ? 'many' : 'one'}",\n`;
152
+ relationsContent += ` "foreignKey": "${rel.foreignKey || 'id'}",\n`;
153
+ relationsContent += ` "localKey": "${rel.localKey || 'id'}"\n`;
154
+ relationsContent += ` },\n`;
155
+ }
156
+ relationsContent += ` }`;
157
+ for (const prop of props) {
158
+ pyContent += ` "${prop}": ${relationsContent},\n`;
159
+ }
160
+ }
161
+ pyContent += '}\n';
162
+ fs_1.default.writeFileSync(this.outputPath, pyContent);
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
+ }
301
+ getAllPropertyVariations(modelName) {
302
+ const variations = new Set();
303
+ variations.add(this.toCamelCase(modelName));
304
+ variations.add(this.toSnakeCase(modelName));
305
+ const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
306
+ for (const acronym of acronyms) {
307
+ if (modelName.startsWith(acronym)) {
308
+ variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
309
+ variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
310
+ variations.add(this.toSnakeCase(acronym.toLowerCase() + modelName.slice(acronym.length)));
311
+ }
312
+ }
313
+ return Array.from(variations);
314
+ }
315
+ formatFields(model) {
316
+ if (model.fields.length === 0)
317
+ return '[]';
318
+ const fields = model.fields.map(f => {
319
+ return ` { "name": "${f.name}", "type": "${f.type}${f.isOptional ? '?' : ''}", "sql": "${f.sqlType}", "isOptional": ${f.isOptional ? 'True' : 'False'}, "hasDefault": ${f.hasDefault ? 'True' : 'False'}, "isId": ${f.isId ? 'True' : 'False'}, "description": ${this.pyString(f.description)} }`;
320
+ });
321
+ return `[\n${fields.join(',\n')}\n ]`;
322
+ }
323
+ pyString(value) {
324
+ return value ? JSON.stringify(value) : 'None';
325
+ }
326
+ toCamelCase(str) {
327
+ if (!str)
328
+ return '';
329
+ return str[0].toLowerCase() + str.slice(1);
330
+ }
331
+ toSnakeCase(str) {
332
+ if (!str)
333
+ return '';
334
+ return str
335
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
336
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
337
+ .toLowerCase();
338
+ }
339
+ capitalize(str) {
340
+ if (!str)
341
+ return '';
342
+ return str[0].toUpperCase() + str.slice(1);
343
+ }
344
+ }
345
+ exports.PythonGenerator = PythonGenerator;
@@ -1,27 +1,27 @@
1
- export interface Field {
2
- name: string;
3
- type: string;
4
- sqlType: string;
5
- isOptional: boolean;
6
- hasDefault: boolean;
7
- isId: boolean;
8
- description?: string;
9
- }
10
- export interface Relation {
11
- name: string;
12
- type: string;
13
- isArray: boolean;
14
- isOptional: boolean;
15
- foreignKey: string;
16
- localKey: string;
17
- relationName: string;
18
- }
19
- export interface Model {
20
- name: string;
21
- tableName: string;
22
- schemaName: string;
23
- fields: Field[];
24
- relations: Relation[];
25
- compoundUniques?: string[][];
26
- description?: string;
27
- }
1
+ export interface Field {
2
+ name: string;
3
+ type: string;
4
+ sqlType: string;
5
+ isOptional: boolean;
6
+ hasDefault: boolean;
7
+ isId: boolean;
8
+ description?: string;
9
+ }
10
+ export interface Relation {
11
+ name: string;
12
+ type: string;
13
+ isArray: boolean;
14
+ isOptional: boolean;
15
+ foreignKey: string;
16
+ localKey: string;
17
+ relationName: string;
18
+ }
19
+ export interface Model {
20
+ name: string;
21
+ tableName: string;
22
+ schemaName: string;
23
+ fields: Field[];
24
+ relations: Relation[];
25
+ compoundUniques?: string[][];
26
+ description?: string;
27
+ }
@@ -1,2 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { An5ORM, an5Orm, default } from "./an5Orm";
2
- export type { Middleware, MiddlewareParams } from "./an5Orm";
3
1
  export { An5ClientKnownRequestError } from "./errors";
4
2
  export { DEFAULT_METADATA } from "./metadata";
5
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";
6
7
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,15 +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.DEFAULT_METADATA = exports.An5ClientKnownRequestError = exports.default = exports.an5Orm = exports.An5ORM = void 0;
7
- var an5Orm_1 = require("./an5Orm");
8
- Object.defineProperty(exports, "An5ORM", { enumerable: true, get: function () { return an5Orm_1.An5ORM; } });
9
- Object.defineProperty(exports, "an5Orm", { enumerable: true, get: function () { return an5Orm_1.an5Orm; } });
10
- Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(an5Orm_1).default; } });
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;
11
4
  var errors_1 = require("./errors");
12
5
  Object.defineProperty(exports, "An5ClientKnownRequestError", { enumerable: true, get: function () { return errors_1.An5ClientKnownRequestError; } });
13
6
  var metadata_1 = require("./metadata");
14
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; } });
15
26
  //# sourceMappingURL=index.js.map
package/dist/logger.js CHANGED
@@ -84,7 +84,7 @@ class Logger {
84
84
  this.log('warn', message, options);
85
85
  }
86
86
  error(message, error, options) {
87
- this.log('error', message, { ...options, error });
87
+ this.log('error', message, { ...options, ...(error !== undefined ? { error } : {}) });
88
88
  }
89
89
  child(context) {
90
90
  return new Logger({