@an5/orm 1.0.7 → 1.0.8
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 +175 -153
- package/dist/an5Metadata.d.ts +10 -0
- package/dist/an5Metadata.js +29 -0
- package/dist/an5Orm.d.ts +4 -1
- package/dist/an5Orm.js +214 -409
- 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 +13 -13
- package/dist/generator/src/code-generator.js +225 -225
- package/dist/generator/src/dotnet-generator.d.ts +11 -11
- package/dist/generator/src/dotnet-generator.js +546 -546
- package/dist/generator/src/index.d.ts +1 -1
- package/dist/generator/src/index.js +88 -88
- package/dist/generator/src/metadata-generator.d.ts +8 -8
- package/dist/generator/src/metadata-generator.js +90 -90
- package/dist/generator/src/parser.d.ts +11 -11
- package/dist/generator/src/parser.js +206 -206
- package/dist/generator/src/python-generator.d.ts +9 -9
- package/dist/generator/src/python-generator.js +105 -105
- package/dist/generator/src/types.d.ts +27 -27
- package/dist/generator/src/types.js +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- 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/scripts/release-cli.d.ts +2 -2
- package/dist/scripts/release-cli.js +115 -115
- package/dist/sql-utils.d.ts +29 -0
- package/dist/sql-utils.js +220 -0
- package/generator/src/code-generator.ts +230 -234
- package/generator/src/dotnet-generator.ts +544 -544
- package/generator/src/index.ts +116 -104
- package/generator/src/metadata-generator.ts +101 -99
- package/generator/src/parser.ts +202 -202
- package/generator/src/python-generator.ts +114 -114
- package/generator/src/types.ts +29 -29
- package/package.json +2 -6
- 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
package/dist/pull.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
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
|
-
require("dotenv/config");
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const an5_adapters_1 = require("@an5/adapters");
|
|
10
|
-
const rootDir = process.cwd();
|
|
11
|
-
let config = {};
|
|
12
|
-
try {
|
|
13
|
-
let configPath = path_1.default.join(rootDir, "an5Orm.config.js");
|
|
14
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
15
|
-
configPath = path_1.default.join(rootDir, "an5Orm.config.cjs");
|
|
16
|
-
}
|
|
17
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
18
|
-
config = require(configPath);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
console.warn("⚠️ Could not load config file in pull.ts, using defaults.");
|
|
23
|
-
}
|
|
24
|
-
const schemaDir = path_1.default.resolve(rootDir, config.schemaDir || "an5Schema");
|
|
25
|
-
let _adapter = null;
|
|
26
|
-
async function getDb() {
|
|
27
|
-
if (!_adapter) {
|
|
28
|
-
_adapter = new an5_adapters_1.An5Adapter({ connectionString: process.env.DATABASE_URL });
|
|
29
|
-
await _adapter.$connect();
|
|
30
|
-
}
|
|
31
|
-
return _adapter;
|
|
32
|
-
}
|
|
33
|
-
function capitalize(str) {
|
|
34
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
35
|
-
}
|
|
36
|
-
async function pull() {
|
|
37
|
-
console.log("🔍 Pulling schema from database...");
|
|
38
|
-
const tables = await (await getDb()).$queryRawUnsafe(`
|
|
39
|
-
SELECT s.name AS schemaName, t.name AS tableName
|
|
40
|
-
FROM sys.tables t
|
|
41
|
-
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
42
|
-
WHERE t.is_ms_shipped = 0
|
|
43
|
-
ORDER BY t.name
|
|
44
|
-
`);
|
|
45
|
-
// Load existing schema files
|
|
46
|
-
const fileContents = {};
|
|
47
|
-
const modelToFileMap = {}; // modelName -> fileName
|
|
48
|
-
const tableToFileMap = {}; // tableName -> fileName
|
|
49
|
-
if (fs_1.default.existsSync(schemaDir)) {
|
|
50
|
-
const files = fs_1.default.readdirSync(schemaDir).filter(f => f.endsWith(".an5"));
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
const fullPath = path_1.default.join(schemaDir, file);
|
|
53
|
-
const text = fs_1.default.readFileSync(fullPath, "utf8");
|
|
54
|
-
fileContents[file] = text;
|
|
55
|
-
// Extract model definitions and table mappings from existing file contents
|
|
56
|
-
const modelRegex = /model\s+(\w+)\s*\{([^}]*)\}/g;
|
|
57
|
-
let match;
|
|
58
|
-
while ((match = modelRegex.exec(text)) !== null) {
|
|
59
|
-
const modelName = match[1];
|
|
60
|
-
const block = match[2];
|
|
61
|
-
modelToFileMap[modelName] = file;
|
|
62
|
-
// Try to find @@map
|
|
63
|
-
const mapMatch = block.match(/@@map\("(.+)"\)/);
|
|
64
|
-
if (mapMatch) {
|
|
65
|
-
const tableName = mapMatch[1];
|
|
66
|
-
tableToFileMap[tableName] = file;
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
// Default pluralization mapping
|
|
70
|
-
const defaultTableName = modelName.toLowerCase() + "s";
|
|
71
|
-
tableToFileMap[defaultTableName] = file;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
fs_1.default.mkdirSync(schemaDir, { recursive: true });
|
|
78
|
-
}
|
|
79
|
-
// Ensure default core.an5 exists in fileContents
|
|
80
|
-
const defaultFile = "core.an5";
|
|
81
|
-
if (!fileContents[defaultFile]) {
|
|
82
|
-
fileContents[defaultFile] = "";
|
|
83
|
-
}
|
|
84
|
-
const existingModelNames = new Set(Object.keys(modelToFileMap));
|
|
85
|
-
const generatedModels = {};
|
|
86
|
-
for (const table of tables) {
|
|
87
|
-
const { tableName } = table;
|
|
88
|
-
console.log(`Introspecting table [${tableName}]...`);
|
|
89
|
-
// Get columns
|
|
90
|
-
const columns = await (await getDb()).$queryRawUnsafe(`
|
|
91
|
-
SELECT
|
|
92
|
-
c.name AS columnName,
|
|
93
|
-
ty.name AS dataType,
|
|
94
|
-
c.max_length AS maxLength,
|
|
95
|
-
c.precision AS precision,
|
|
96
|
-
c.scale AS scale,
|
|
97
|
-
c.is_nullable AS isNullable,
|
|
98
|
-
c.is_identity AS isIdentity,
|
|
99
|
-
pk.is_primary_key AS isPrimaryKey,
|
|
100
|
-
d.definition AS defaultValue
|
|
101
|
-
FROM sys.columns c
|
|
102
|
-
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
|
|
103
|
-
LEFT JOIN (
|
|
104
|
-
SELECT ic.object_id, ic.column_id, i.is_primary_key
|
|
105
|
-
FROM sys.index_columns ic
|
|
106
|
-
JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
107
|
-
WHERE i.is_primary_key = 1
|
|
108
|
-
) pk ON c.object_id = pk.object_id AND c.column_id = pk.column_id
|
|
109
|
-
LEFT JOIN sys.default_constraints d ON c.default_object_id = d.object_id
|
|
110
|
-
WHERE c.object_id = OBJECT_ID(@p_0)
|
|
111
|
-
ORDER BY c.column_id
|
|
112
|
-
`, tableName);
|
|
113
|
-
// Get indexes
|
|
114
|
-
const indexes = await (await getDb()).$queryRawUnsafe(`
|
|
115
|
-
SELECT
|
|
116
|
-
i.name AS indexName,
|
|
117
|
-
i.is_unique AS isUnique,
|
|
118
|
-
i.is_primary_key AS isPrimaryKey,
|
|
119
|
-
c.name AS columnName,
|
|
120
|
-
ic.key_ordinal AS keyOrdinal
|
|
121
|
-
FROM sys.indexes i
|
|
122
|
-
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
123
|
-
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
|
124
|
-
WHERE i.object_id = OBJECT_ID(@p_0) AND i.is_hypothetical = 0 AND i.name IS NOT NULL
|
|
125
|
-
ORDER BY i.name, ic.key_ordinal
|
|
126
|
-
`, tableName);
|
|
127
|
-
const indexGroups = {};
|
|
128
|
-
for (const idx of indexes) {
|
|
129
|
-
if (!indexGroups[idx.indexName]) {
|
|
130
|
-
indexGroups[idx.indexName] = {
|
|
131
|
-
isUnique: idx.isUnique === 1 || idx.isUnique === true,
|
|
132
|
-
isPrimaryKey: idx.isPrimaryKey === 1 || idx.isPrimaryKey === true,
|
|
133
|
-
columns: []
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
indexGroups[idx.indexName].columns.push(idx.columnName);
|
|
137
|
-
}
|
|
138
|
-
const uniqueColumns = new Set();
|
|
139
|
-
const compoundUniques = [];
|
|
140
|
-
const modelIndexes = [];
|
|
141
|
-
for (const [name, info] of Object.entries(indexGroups)) {
|
|
142
|
-
if (info.isPrimaryKey)
|
|
143
|
-
continue;
|
|
144
|
-
if (info.isUnique) {
|
|
145
|
-
if (info.columns.length === 1) {
|
|
146
|
-
uniqueColumns.add(info.columns[0]);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
compoundUniques.push(info.columns);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
modelIndexes.push(info.columns);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
let modelName = "";
|
|
157
|
-
for (const [mName, file] of Object.entries(modelToFileMap)) {
|
|
158
|
-
const text = fileContents[file];
|
|
159
|
-
const modelRegex = new RegExp(`model\\s+(${mName})\\s*\\{[^}]*\\}`, 'g');
|
|
160
|
-
let m;
|
|
161
|
-
while ((m = modelRegex.exec(text)) !== null) {
|
|
162
|
-
const block = m[0];
|
|
163
|
-
const mapMatch = block.match(/@@map\("(.+)"\)/);
|
|
164
|
-
if (mapMatch && mapMatch[1] === tableName) {
|
|
165
|
-
modelName = mName;
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (modelName)
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
if (!modelName) {
|
|
173
|
-
modelName = tableName.charAt(0).toUpperCase() + tableName.slice(1).replace(/s$/, "");
|
|
174
|
-
}
|
|
175
|
-
// Parse existing model fields from file for merging
|
|
176
|
-
const existingFields = {};
|
|
177
|
-
const targetFile = modelToFileMap[modelName] || defaultFile;
|
|
178
|
-
const fileContent = fileContents[targetFile];
|
|
179
|
-
if (fileContent) {
|
|
180
|
-
const modelRegex = new RegExp(`model\\s+${modelName}\\s*\\{([^}]*)\\}`, 'g');
|
|
181
|
-
const match = modelRegex.exec(fileContent);
|
|
182
|
-
if (match) {
|
|
183
|
-
const block = match[1];
|
|
184
|
-
const lines = block.split("\n");
|
|
185
|
-
for (let line of lines) {
|
|
186
|
-
line = line.trim();
|
|
187
|
-
if (!line || line.startsWith("@@") || line.startsWith("model ") || line === "}")
|
|
188
|
-
continue;
|
|
189
|
-
const parts = line.split(/\s+/);
|
|
190
|
-
const name = parts[0];
|
|
191
|
-
const type = parts[1];
|
|
192
|
-
if (!name || !type)
|
|
193
|
-
continue;
|
|
194
|
-
const cleanType = type.replace("[]", "").replace("?", "");
|
|
195
|
-
const isRelation = existingModelNames.has(cleanType) || type.endsWith("[]");
|
|
196
|
-
existingFields[name.toLowerCase()] = {
|
|
197
|
-
line,
|
|
198
|
-
isRelation,
|
|
199
|
-
name
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
let modelStr = `model ${modelName} {\n`;
|
|
205
|
-
for (const col of columns) {
|
|
206
|
-
const { columnName, dataType, isNullable, isPrimaryKey, isIdentity, defaultValue } = col;
|
|
207
|
-
// Generate SQL Server type directly
|
|
208
|
-
let sqlType = dataType.toUpperCase();
|
|
209
|
-
const dt = dataType.toLowerCase();
|
|
210
|
-
// Add length/precision params
|
|
211
|
-
if (dt === "nvarchar" || dt === "varchar") {
|
|
212
|
-
sqlType = col.maxLength === -1 ? `${sqlType.toUpperCase()}(MAX)` : `${sqlType.toUpperCase()}(${col.maxLength / 2})`;
|
|
213
|
-
}
|
|
214
|
-
else if (dt === "char" || dt === "nchar") {
|
|
215
|
-
sqlType = `${sqlType.toUpperCase()}(${col.maxLength / 2})`;
|
|
216
|
-
}
|
|
217
|
-
else if (dt === "varbinary" || dt === "binary") {
|
|
218
|
-
sqlType = col.maxLength === -1 ? `${sqlType.toUpperCase()}(MAX)` : `${sqlType.toUpperCase()}(${col.maxLength})`;
|
|
219
|
-
}
|
|
220
|
-
else if (dt === "decimal" || dt === "numeric") {
|
|
221
|
-
sqlType = `${sqlType.toUpperCase()}(${col.precision}, ${col.scale})`;
|
|
222
|
-
}
|
|
223
|
-
const optional = isNullable ? "?" : "";
|
|
224
|
-
let attributes = "";
|
|
225
|
-
if (isPrimaryKey)
|
|
226
|
-
attributes += " @id";
|
|
227
|
-
if (isIdentity)
|
|
228
|
-
attributes += " @default(autoincrement())";
|
|
229
|
-
if (uniqueColumns.has(columnName) && !isPrimaryKey)
|
|
230
|
-
attributes += " @unique";
|
|
231
|
-
if (defaultValue) {
|
|
232
|
-
if (defaultValue.includes("newid()")) {
|
|
233
|
-
attributes += " @default(uuid())";
|
|
234
|
-
}
|
|
235
|
-
else if (defaultValue.includes("getdate()") || defaultValue.includes("sysdatetime()")) {
|
|
236
|
-
attributes += " @default(now())";
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
const cleaned = defaultValue.replace(/^\(+(.*?)\)+$/, '$1');
|
|
240
|
-
if (dt === "bit") {
|
|
241
|
-
if (cleaned === "1" || cleaned.toLowerCase() === "true")
|
|
242
|
-
attributes += " @default(true)";
|
|
243
|
-
else if (cleaned === "0" || cleaned.toLowerCase() === "false")
|
|
244
|
-
attributes += " @default(false)";
|
|
245
|
-
}
|
|
246
|
-
else if (dt === "int" || dt === "float" || dt === "bigint" || dt === "decimal" || dt === "numeric") {
|
|
247
|
-
if (!isNaN(Number(cleaned)))
|
|
248
|
-
attributes += ` @default(${cleaned})`;
|
|
249
|
-
}
|
|
250
|
-
else if (dt === "nvarchar" || dt === "varchar") {
|
|
251
|
-
if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
|
|
252
|
-
const val = cleaned.slice(1, -1).replace(/"/g, '\\"');
|
|
253
|
-
attributes += ` @default("${val}")`;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
let fieldLine = `${columnName} ${sqlType}${optional}${attributes}`;
|
|
259
|
-
// Merge decorators/attributes from existing schema
|
|
260
|
-
const existing = existingFields[columnName.toLowerCase()];
|
|
261
|
-
if (existing) {
|
|
262
|
-
const attributesMatch = existing.line.match(/(@default\(.*?\)|@updatedAt)/g);
|
|
263
|
-
if (attributesMatch) {
|
|
264
|
-
for (const attr of attributesMatch) {
|
|
265
|
-
if (!fieldLine.includes(attr)) {
|
|
266
|
-
if (attr.startsWith("@default") && fieldLine.includes("@default")) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
fieldLine += ` ${attr}`;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
modelStr += ` ${fieldLine}\n`;
|
|
275
|
-
}
|
|
276
|
-
// Append preserved relations
|
|
277
|
-
for (const [lowerName, fieldInfo] of Object.entries(existingFields)) {
|
|
278
|
-
if (fieldInfo.isRelation) {
|
|
279
|
-
modelStr += ` ${fieldInfo.line}\n`;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const uniqueStrings = new Set();
|
|
283
|
-
for (const cols of compoundUniques) {
|
|
284
|
-
uniqueStrings.add(`@@unique([${cols.join(", ")}])`);
|
|
285
|
-
}
|
|
286
|
-
for (const key of uniqueStrings) {
|
|
287
|
-
modelStr += ` ${key}\n`;
|
|
288
|
-
}
|
|
289
|
-
const indexStrings = new Set();
|
|
290
|
-
for (const cols of modelIndexes) {
|
|
291
|
-
indexStrings.add(`@@index([${cols.join(", ")}])`);
|
|
292
|
-
}
|
|
293
|
-
for (const key of indexStrings) {
|
|
294
|
-
modelStr += ` ${key}\n`;
|
|
295
|
-
}
|
|
296
|
-
if (tableName !== modelName.toLowerCase() + "s") {
|
|
297
|
-
modelStr += `\n @@map("${tableName}")\n`;
|
|
298
|
-
}
|
|
299
|
-
modelStr += "}";
|
|
300
|
-
generatedModels[modelName] = modelStr;
|
|
301
|
-
}
|
|
302
|
-
for (const [modelName, modelStr] of Object.entries(generatedModels)) {
|
|
303
|
-
const targetFile = modelToFileMap[modelName] || defaultFile;
|
|
304
|
-
const fileContent = fileContents[targetFile];
|
|
305
|
-
const modelRegex = new RegExp(`model\\s+${modelName}\\s*\\{[^}]*\\}`, 'g');
|
|
306
|
-
if (modelRegex.test(fileContent)) {
|
|
307
|
-
fileContents[targetFile] = fileContent.replace(modelRegex, modelStr);
|
|
308
|
-
console.log(`✏️ Updated model [${modelName}] in file [${targetFile}]`);
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
fileContents[targetFile] = fileContent.trim() + "\n\n" + modelStr + "\n";
|
|
312
|
-
console.log(`➕ Added model [${modelName}] to file [${targetFile}]`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
for (const [file, content] of Object.entries(fileContents)) {
|
|
316
|
-
const fullPath = path_1.default.join(schemaDir, file);
|
|
317
|
-
fs_1.default.writeFileSync(fullPath, content.trim() + "\n");
|
|
318
|
-
console.log(`💾 Saved updates to [${fullPath}]`);
|
|
319
|
-
}
|
|
320
|
-
// Index schema into vector store for RAG
|
|
321
|
-
console.log("\n📚 Indexing schema into vector store for RAG...");
|
|
322
|
-
try {
|
|
323
|
-
const { indexSchema, indexQuerySamples } = require(path_1.default.join(rootDir, "an5Agent", "dist", "rag", "indexer.js"));
|
|
324
|
-
await indexSchema(schemaDir);
|
|
325
|
-
await indexQuerySamples();
|
|
326
|
-
console.log("✅ Schema indexed for RAG.");
|
|
327
|
-
}
|
|
328
|
-
catch (err) {
|
|
329
|
-
console.warn(`⚠️ RAG indexing skipped: ${err.message || err}`);
|
|
330
|
-
console.warn(` Run 'npm --prefix an5Agent run rag:index' manually to build vector store.`);
|
|
331
|
-
}
|
|
332
|
-
console.log("✅ Database schema pulling completed successfully.");
|
|
333
|
-
process.exit(0);
|
|
334
|
-
}
|
|
335
|
-
pull().catch((err) => {
|
|
336
|
-
console.error("❌ Pull failed:", err);
|
|
337
|
-
process.exit(1);
|
|
338
|
-
});
|
|
339
|
-
//# sourceMappingURL=pull.js.map
|
package/dist/push.d.ts
DELETED
package/dist/push.js
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
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
|
-
require("dotenv/config");
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const adapters_1 = require("@an5/adapters");
|
|
10
|
-
const rootDir = process.cwd();
|
|
11
|
-
let config = {};
|
|
12
|
-
try {
|
|
13
|
-
let configPath = path_1.default.join(rootDir, "an5Orm.config.js");
|
|
14
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
15
|
-
configPath = path_1.default.join(rootDir, "an5Orm.config.cjs");
|
|
16
|
-
}
|
|
17
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
18
|
-
config = require(configPath);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
console.warn("⚠️ Could not load config file in push.ts, using defaults.");
|
|
23
|
-
}
|
|
24
|
-
const schemaDir = path_1.default.resolve(rootDir, config.schemaDir || "an5Schema");
|
|
25
|
-
let _adapter = null;
|
|
26
|
-
async function getDb() {
|
|
27
|
-
if (!_adapter) {
|
|
28
|
-
_adapter = new adapters_1.An5Adapter({ connectionString: process.env.DATABASE_URL });
|
|
29
|
-
await _adapter.$connect();
|
|
30
|
-
}
|
|
31
|
-
return _adapter;
|
|
32
|
-
}
|
|
33
|
-
// Supported SQL Server types (base types without params)
|
|
34
|
-
const AN5_TYPES = new Set([
|
|
35
|
-
"NVARCHAR", "VARCHAR", "CHAR", "NCHAR", "TEXT", "NTEXT", "XML",
|
|
36
|
-
"INT", "SMALLINT", "TINYINT", "BIGINT", "FLOAT", "REAL", "DECIMAL", "NUMERIC",
|
|
37
|
-
"MONEY", "SMALLMONEY", "BIT",
|
|
38
|
-
"DATETIME", "DATETIME2", "SMALLDATETIME", "DATE", "TIME", "DATETIMEOFFSET",
|
|
39
|
-
"VARBINARY", "BINARY", "IMAGE",
|
|
40
|
-
"UNIQUEIDENTIFIER", "SQL_VARIANT", "ROWVERSION",
|
|
41
|
-
"HIERARCHYID", "GEOGRAPHY", "GEOMETRY", "VECTOR",
|
|
42
|
-
]);
|
|
43
|
-
// Parse base type from "NVARCHAR(255)" → "NVARCHAR"
|
|
44
|
-
function parseSqlType(raw) {
|
|
45
|
-
const match = raw.match(/^(\w+)/);
|
|
46
|
-
return match ? match[1].toUpperCase() : raw.toUpperCase();
|
|
47
|
-
}
|
|
48
|
-
function parseTableName(raw) {
|
|
49
|
-
const cleaned = raw.replace(/[\[\]"]/g, "");
|
|
50
|
-
const parts = cleaned.split(".").filter(Boolean);
|
|
51
|
-
if (parts.length >= 2) {
|
|
52
|
-
return { schema: parts[parts.length - 2], name: parts[parts.length - 1] };
|
|
53
|
-
}
|
|
54
|
-
return { name: cleaned };
|
|
55
|
-
}
|
|
56
|
-
function tableSqlName(raw) {
|
|
57
|
-
const table = parseTableName(raw);
|
|
58
|
-
return table.schema ? `[${table.schema}].[${table.name}]` : `[${table.name}]`;
|
|
59
|
-
}
|
|
60
|
-
function tableObjectName(raw) {
|
|
61
|
-
const table = parseTableName(raw);
|
|
62
|
-
return table.schema ? `${table.schema}.${table.name}` : table.name;
|
|
63
|
-
}
|
|
64
|
-
function safeIdentifierName(raw) {
|
|
65
|
-
return raw.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
66
|
-
}
|
|
67
|
-
async function push() {
|
|
68
|
-
let schemaText = "";
|
|
69
|
-
if (fs_1.default.existsSync(schemaDir)) {
|
|
70
|
-
const files = fs_1.default.readdirSync(schemaDir).filter(f => f.endsWith(".an5"));
|
|
71
|
-
for (const file of files) {
|
|
72
|
-
schemaText += fs_1.default.readFileSync(path_1.default.join(schemaDir, file), "utf8") + "\n";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
const schemaPath = path_1.default.join(__dirname, "schema.an5");
|
|
77
|
-
if (fs_1.default.existsSync(schemaPath)) {
|
|
78
|
-
schemaText = fs_1.default.readFileSync(schemaPath, "utf8");
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
console.error(`No schema found in ${schemaDir} or schema.an5`);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const lines = schemaText.split("\n");
|
|
86
|
-
const models = [];
|
|
87
|
-
let currentModel = null;
|
|
88
|
-
for (let line of lines) {
|
|
89
|
-
line = line.trim();
|
|
90
|
-
if (!line || line.startsWith("//"))
|
|
91
|
-
continue;
|
|
92
|
-
const modelHeaderMatch = line.match(/^model\s+(\w+)\s*\{/);
|
|
93
|
-
if (modelHeaderMatch) {
|
|
94
|
-
currentModel = {
|
|
95
|
-
name: modelHeaderMatch[1],
|
|
96
|
-
tableName: modelHeaderMatch[1].toLowerCase() + "s",
|
|
97
|
-
fields: [],
|
|
98
|
-
compoundUniques: [],
|
|
99
|
-
indexes: [],
|
|
100
|
-
};
|
|
101
|
-
models.push(currentModel);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (line === "}") {
|
|
105
|
-
currentModel = null;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (currentModel) {
|
|
109
|
-
if (line.startsWith("@@map")) {
|
|
110
|
-
const mapMatch = line.match(/@@map\("(.+)"\)/);
|
|
111
|
-
if (mapMatch)
|
|
112
|
-
currentModel.tableName = mapMatch[1];
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (line.startsWith("@@unique")) {
|
|
116
|
-
const uniqueMatch = line.match(/@@unique\(\[([\w,\s]+)\]\)/);
|
|
117
|
-
if (uniqueMatch) {
|
|
118
|
-
const fields = uniqueMatch[1].split(",").map(f => f.trim());
|
|
119
|
-
currentModel.compoundUniques.push(fields);
|
|
120
|
-
}
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (line.startsWith("@@index")) {
|
|
124
|
-
const indexMatch = line.match(/@@index\(\[([\w,\s]+)\]\)/);
|
|
125
|
-
if (indexMatch) {
|
|
126
|
-
const fields = indexMatch[1].split(",").map(f => f.trim());
|
|
127
|
-
currentModel.indexes.push(fields);
|
|
128
|
-
}
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (line.startsWith("@@"))
|
|
132
|
-
continue;
|
|
133
|
-
const parts = line.split(/\s+/);
|
|
134
|
-
const fieldName = parts[0];
|
|
135
|
-
const fieldType = parts[1];
|
|
136
|
-
if (!fieldName || !fieldType)
|
|
137
|
-
continue;
|
|
138
|
-
// Parse SQL Server type directly
|
|
139
|
-
const cleanType = fieldType.replace("[]", "").replace("?", "");
|
|
140
|
-
const sqlBase = parseSqlType(cleanType);
|
|
141
|
-
// Skip if not a known SQL Server type (might be a relation)
|
|
142
|
-
if (!AN5_TYPES.has(sqlBase)) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
const isOptional = fieldType.endsWith("?");
|
|
146
|
-
const isId = line.includes("@id");
|
|
147
|
-
const isUnique = line.includes("@unique");
|
|
148
|
-
// Use SQL Server type directly - no mapping needed
|
|
149
|
-
const sqlType = cleanType.toUpperCase();
|
|
150
|
-
let defaultValue = "";
|
|
151
|
-
const defaultMatch = line.match(/@default\((.*)\)/);
|
|
152
|
-
if (defaultMatch) {
|
|
153
|
-
const val = defaultMatch[1];
|
|
154
|
-
if (val === "uuid()")
|
|
155
|
-
defaultValue = "DEFAULT NEWID()";
|
|
156
|
-
else if (val === "cuid()")
|
|
157
|
-
defaultValue = "DEFAULT NEWID()";
|
|
158
|
-
else if (val === "now()")
|
|
159
|
-
defaultValue = "DEFAULT CURRENT_TIMESTAMP";
|
|
160
|
-
else if (val === "autoincrement()")
|
|
161
|
-
defaultValue = "IDENTITY(1,1)";
|
|
162
|
-
else if (val === "true")
|
|
163
|
-
defaultValue = "DEFAULT 1";
|
|
164
|
-
else if (val === "false")
|
|
165
|
-
defaultValue = "DEFAULT 0";
|
|
166
|
-
else if (/^".*"$/.test(val))
|
|
167
|
-
defaultValue = `DEFAULT '${val.slice(1, -1)}'`;
|
|
168
|
-
else
|
|
169
|
-
defaultValue = `DEFAULT ${val}`;
|
|
170
|
-
}
|
|
171
|
-
else if (line.includes("@updatedAt")) {
|
|
172
|
-
defaultValue = "DEFAULT CURRENT_TIMESTAMP";
|
|
173
|
-
}
|
|
174
|
-
currentModel.fields.push({
|
|
175
|
-
name: fieldName,
|
|
176
|
-
sqlType,
|
|
177
|
-
isOptional,
|
|
178
|
-
isId,
|
|
179
|
-
isUnique,
|
|
180
|
-
defaultValue,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
console.log(`🚀 Pushing schema to database...`);
|
|
185
|
-
for (const model of models) {
|
|
186
|
-
const sqlTableName = tableSqlName(model.tableName);
|
|
187
|
-
const objectName = tableObjectName(model.tableName);
|
|
188
|
-
console.log(`Processing table ${sqlTableName}...`);
|
|
189
|
-
// Check if table exists
|
|
190
|
-
const tableExists = await (await getDb()).$queryRawUnsafe(`SELECT name FROM sys.tables WHERE object_id = OBJECT_ID('${objectName}')`);
|
|
191
|
-
if (tableExists.length === 0) {
|
|
192
|
-
console.log(`Creating table ${sqlTableName}...`);
|
|
193
|
-
const colDefs = model.fields.map((f) => {
|
|
194
|
-
let def = `[${f.name}] ${f.sqlType}`;
|
|
195
|
-
if (f.isId)
|
|
196
|
-
def += " PRIMARY KEY";
|
|
197
|
-
if (f.defaultValue)
|
|
198
|
-
def += ` ${f.defaultValue}`;
|
|
199
|
-
if (!f.isOptional && !f.defaultValue && !f.isId)
|
|
200
|
-
def += " NOT NULL";
|
|
201
|
-
if (f.isUnique && !f.isId)
|
|
202
|
-
def += " UNIQUE";
|
|
203
|
-
return def;
|
|
204
|
-
});
|
|
205
|
-
// Add compound uniques as table constraints
|
|
206
|
-
if (model.compoundUniques && model.compoundUniques.length > 0) {
|
|
207
|
-
model.compoundUniques.forEach((fields, idx) => {
|
|
208
|
-
const constraintName = `UQ_${safeIdentifierName(model.tableName)}_compound_${idx}`;
|
|
209
|
-
const fieldsStr = fields.map(f => `[${f}]`).join(", ");
|
|
210
|
-
colDefs.push(`CONSTRAINT [${constraintName}] UNIQUE (${fieldsStr})`);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
const createSql = `CREATE TABLE ${sqlTableName} (\n ${colDefs.join(",\n ")}\n)`;
|
|
214
|
-
await (await getDb()).$executeRawUnsafe(createSql);
|
|
215
|
-
console.log(`✅ Table ${sqlTableName} created.`);
|
|
216
|
-
// Create indexes
|
|
217
|
-
if (model.indexes && model.indexes.length > 0) {
|
|
218
|
-
for (let idx = 0; idx < model.indexes.length; idx++) {
|
|
219
|
-
const fields = model.indexes[idx];
|
|
220
|
-
const indexName = `IX_${safeIdentifierName(model.tableName)}_${fields.join("_")}`;
|
|
221
|
-
const fieldsStr = fields.map((f) => `[${f}]`).join(", ");
|
|
222
|
-
console.log(`Creating index [${indexName}] on table ${sqlTableName}...`);
|
|
223
|
-
await (await getDb()).$executeRawUnsafe(`CREATE INDEX [${indexName}] ON ${sqlTableName} (${fieldsStr})`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
// 1. Check for missing columns
|
|
229
|
-
for (const field of model.fields) {
|
|
230
|
-
const colExists = await (await getDb()).$queryRawUnsafe(`SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('${objectName}') AND name = '${field.name}'`);
|
|
231
|
-
if (colExists.length === 0) {
|
|
232
|
-
console.log(`Adding column [${field.name}] to table ${sqlTableName}...`);
|
|
233
|
-
let alterSql = `ALTER TABLE ${sqlTableName} ADD [${field.name}] ${field.sqlType}`;
|
|
234
|
-
if (field.defaultValue)
|
|
235
|
-
alterSql += ` ${field.defaultValue}`;
|
|
236
|
-
if (field.isUnique && !field.isId)
|
|
237
|
-
alterSql += " UNIQUE";
|
|
238
|
-
if (!field.isOptional && !field.defaultValue) {
|
|
239
|
-
if (!field.defaultValue)
|
|
240
|
-
alterSql += " NULL";
|
|
241
|
-
else
|
|
242
|
-
alterSql += " NOT NULL";
|
|
243
|
-
}
|
|
244
|
-
await (await getDb()).$executeRawUnsafe(alterSql);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// 2. Check for missing compound uniques
|
|
248
|
-
if (model.compoundUniques && model.compoundUniques.length > 0) {
|
|
249
|
-
for (let idx = 0; idx < model.compoundUniques.length; idx++) {
|
|
250
|
-
const fields = model.compoundUniques[idx];
|
|
251
|
-
const constraintName = `UQ_${safeIdentifierName(model.tableName)}_compound_${idx}`;
|
|
252
|
-
const constraintExists = await (await getDb()).$queryRawUnsafe(`SELECT name FROM sys.objects WHERE type = 'UQ' AND parent_object_id = OBJECT_ID('${objectName}') AND name = '${constraintName}'`);
|
|
253
|
-
if (constraintExists.length === 0) {
|
|
254
|
-
console.log(`Adding compound unique constraint [${constraintName}] to table ${sqlTableName}...`);
|
|
255
|
-
const fieldsStr = fields.map((f) => `[${f}]`).join(", ");
|
|
256
|
-
await (await getDb()).$executeRawUnsafe(`ALTER TABLE ${sqlTableName} ADD CONSTRAINT [${constraintName}] UNIQUE (${fieldsStr})`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// 3. Check for missing indexes
|
|
261
|
-
if (model.indexes && model.indexes.length > 0) {
|
|
262
|
-
for (let idx = 0; idx < model.indexes.length; idx++) {
|
|
263
|
-
const fields = model.indexes[idx];
|
|
264
|
-
const indexName = `IX_${safeIdentifierName(model.tableName)}_${fields.join("_")}`;
|
|
265
|
-
const indexExists = await (await getDb()).$queryRawUnsafe(`SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('${objectName}') AND name = '${indexName}'`);
|
|
266
|
-
if (indexExists.length === 0) {
|
|
267
|
-
console.log(`Creating index [${indexName}] on table ${sqlTableName}...`);
|
|
268
|
-
const fieldsStr = fields.map((f) => `[${f}]`).join(", ");
|
|
269
|
-
await (await getDb()).$executeRawUnsafe(`CREATE INDEX [${indexName}] ON ${sqlTableName} (${fieldsStr})`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
console.log("✅ Database push completed.");
|
|
276
|
-
process.exit(0);
|
|
277
|
-
}
|
|
278
|
-
push().catch((err) => {
|
|
279
|
-
console.error("❌ Push failed:", err);
|
|
280
|
-
process.exit(1);
|
|
281
|
-
});
|
package/dist/seed.d.ts
DELETED