flint-orm 0.1.0 → 0.2.0
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/dist/index.js +147 -4
- package/package.json +13 -2
- package/src/cli.ts +306 -0
- package/dist/entries/expressions.d.ts +0 -5
- package/dist/entries/expressions.js +0 -1184
- package/dist/entries/table.d.ts +0 -4
- package/dist/entries/table.js +0 -302
- package/dist/errors.d.ts +0 -13
- package/dist/flint.d.ts +0 -128
- package/dist/index.d.ts +0 -9
- package/dist/query/aggregates.d.ts +0 -47
- package/dist/query/builder.d.ts +0 -256
- package/dist/query/conditions.d.ts +0 -176
- package/dist/schema/columns.d.ts +0 -137
- package/dist/schema/table.d.ts +0 -44
- package/dist/src/entries/expressions.d.ts +0 -5
- package/dist/src/entries/table.d.ts +0 -4
- package/dist/src/errors.d.ts +0 -13
- package/dist/src/flint.d.ts +0 -128
- package/dist/src/index.d.ts +0 -9
- package/dist/src/query/aggregates.d.ts +0 -47
- package/dist/src/query/builder.d.ts +0 -256
- package/dist/src/query/conditions.d.ts +0 -176
- package/dist/src/schema/columns.d.ts +0 -137
- package/dist/src/schema/table.d.ts +0 -44
package/dist/index.js
CHANGED
|
@@ -1144,6 +1144,9 @@ function flint(details) {
|
|
|
1144
1144
|
avg: (table, column, condition) => avg(client, table, column, condition),
|
|
1145
1145
|
min: (table, column, condition) => min(client, table, column, condition),
|
|
1146
1146
|
max: (table, column, condition) => max(client, table, column, condition),
|
|
1147
|
+
$run(sql, ...params) {
|
|
1148
|
+
return client.prepare(sql).run(...params);
|
|
1149
|
+
},
|
|
1147
1150
|
$client: client
|
|
1148
1151
|
};
|
|
1149
1152
|
}
|
|
@@ -1329,7 +1332,7 @@ function real(name) {
|
|
|
1329
1332
|
});
|
|
1330
1333
|
}
|
|
1331
1334
|
// src/schema/table.ts
|
|
1332
|
-
function table(name, columns) {
|
|
1335
|
+
function table(name, columns, indexFn) {
|
|
1333
1336
|
const stamped = Object.create(null);
|
|
1334
1337
|
for (const [key, col] of Object.entries(columns)) {
|
|
1335
1338
|
const columnName = col.name || key;
|
|
@@ -1383,14 +1386,22 @@ function table(name, columns) {
|
|
|
1383
1386
|
}
|
|
1384
1387
|
stamped[key] = stampedCol;
|
|
1385
1388
|
}
|
|
1386
|
-
|
|
1389
|
+
const result = Object.assign(stamped, {
|
|
1387
1390
|
_: { name }
|
|
1388
1391
|
});
|
|
1392
|
+
if (indexFn) {
|
|
1393
|
+
const raw = indexFn(result);
|
|
1394
|
+
if (raw.length > 0) {
|
|
1395
|
+
const tableObj = result;
|
|
1396
|
+
tableObj.__indexes = raw.map((item) => item.build());
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return result;
|
|
1389
1400
|
}
|
|
1390
1401
|
function toSnakeCase(str) {
|
|
1391
1402
|
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
1392
1403
|
}
|
|
1393
|
-
function snakeCaseTable(tableName, columns) {
|
|
1404
|
+
function snakeCaseTable(tableName, columns, indexFn) {
|
|
1394
1405
|
const converted = {};
|
|
1395
1406
|
for (const [key, col] of Object.entries(columns)) {
|
|
1396
1407
|
const sqlName = toSnakeCase(key);
|
|
@@ -1445,11 +1456,141 @@ function snakeCaseTable(tableName, columns) {
|
|
|
1445
1456
|
}
|
|
1446
1457
|
converted[key] = stampedCol;
|
|
1447
1458
|
}
|
|
1448
|
-
return table(tableName, converted);
|
|
1459
|
+
return table(tableName, converted, indexFn);
|
|
1449
1460
|
}
|
|
1450
1461
|
var snakeCase = {
|
|
1451
1462
|
table: snakeCaseTable
|
|
1452
1463
|
};
|
|
1464
|
+
function index(name) {
|
|
1465
|
+
let columns = [];
|
|
1466
|
+
let isUnique = false;
|
|
1467
|
+
const builder = {
|
|
1468
|
+
on(...cols) {
|
|
1469
|
+
columns = cols;
|
|
1470
|
+
return builder;
|
|
1471
|
+
},
|
|
1472
|
+
unique() {
|
|
1473
|
+
isUnique = true;
|
|
1474
|
+
return builder;
|
|
1475
|
+
},
|
|
1476
|
+
build() {
|
|
1477
|
+
if (columns.length === 0) {
|
|
1478
|
+
throw new Error(`Index "${name}" has no columns \u2014 call .on() before returning`);
|
|
1479
|
+
}
|
|
1480
|
+
return {
|
|
1481
|
+
name,
|
|
1482
|
+
columns: columns.map((c) => c.name),
|
|
1483
|
+
unique: isUnique
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
return builder;
|
|
1488
|
+
}
|
|
1489
|
+
// src/sqlite/introspect.ts
|
|
1490
|
+
var TEXT_TYPES = new Set(["text", "varchar", "character", "char", "clob", "native character", "nvarchar", "nchar", "nyclob", "numeric"]);
|
|
1491
|
+
var INTEGER_TYPES = new Set(["integer", "int", "smallint", "tinyint", "bigint", "unsigned big int", "int2", "int8", "mediumint"]);
|
|
1492
|
+
var REAL_TYPES = new Set(["real", "float", "double", "double precision", "decimal"]);
|
|
1493
|
+
function normalizeType(rawType) {
|
|
1494
|
+
const t = rawType.toLowerCase().trim();
|
|
1495
|
+
const base = t.replace(/\(.*\)/, "").trim();
|
|
1496
|
+
if (INTEGER_TYPES.has(base))
|
|
1497
|
+
return "integer";
|
|
1498
|
+
if (REAL_TYPES.has(base))
|
|
1499
|
+
return "real";
|
|
1500
|
+
if (TEXT_TYPES.has(base))
|
|
1501
|
+
return "text";
|
|
1502
|
+
if (base === "blob")
|
|
1503
|
+
return "blob";
|
|
1504
|
+
return "text";
|
|
1505
|
+
}
|
|
1506
|
+
function parseDefault(dfltValue) {
|
|
1507
|
+
if (dfltValue === null || dfltValue === undefined) {
|
|
1508
|
+
return { hasDefault: false };
|
|
1509
|
+
}
|
|
1510
|
+
const raw = String(dfltValue);
|
|
1511
|
+
if (raw.toUpperCase() === "NULL") {
|
|
1512
|
+
return { hasDefault: true, defaultValue: null };
|
|
1513
|
+
}
|
|
1514
|
+
if (/^-?\d+$/.test(raw)) {
|
|
1515
|
+
return { hasDefault: true, defaultValue: Number(raw) };
|
|
1516
|
+
}
|
|
1517
|
+
if (/^-?\d+\.\d+$/.test(raw)) {
|
|
1518
|
+
return { hasDefault: true, defaultValue: Number(raw) };
|
|
1519
|
+
}
|
|
1520
|
+
if (raw.startsWith("'") && raw.endsWith("'") || raw.startsWith('"') && raw.endsWith('"')) {
|
|
1521
|
+
return { hasDefault: true, defaultValue: raw.slice(1, -1) };
|
|
1522
|
+
}
|
|
1523
|
+
return { hasDefault: true, defaultValue: raw };
|
|
1524
|
+
}
|
|
1525
|
+
function introspectColumns(client, tableName) {
|
|
1526
|
+
const rows = client.query(`PRAGMA table_info('${tableName}')`).all();
|
|
1527
|
+
const fkRows = client.query(`PRAGMA foreign_key_list('${tableName}')`).all();
|
|
1528
|
+
const indexRows = client.query(`PRAGMA index_list('${tableName}')`).all();
|
|
1529
|
+
const fkMap = new Map;
|
|
1530
|
+
for (const fk of fkRows) {
|
|
1531
|
+
fkMap.set(fk.from, { referencesTable: fk.table, referencesColumn: fk.to });
|
|
1532
|
+
}
|
|
1533
|
+
const uniqueColumns = new Set;
|
|
1534
|
+
for (const idx of indexRows) {
|
|
1535
|
+
if (idx.unique === 1 && (idx.origin === "u" || idx.origin === "c")) {
|
|
1536
|
+
const colInfo = client.query(`PRAGMA index_info('${idx.name}')`).all();
|
|
1537
|
+
if (colInfo.length === 1) {
|
|
1538
|
+
uniqueColumns.add(colInfo[0].name);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
const columns = rows.map((row) => {
|
|
1543
|
+
const fk = fkMap.get(row.name);
|
|
1544
|
+
const { hasDefault, defaultValue } = parseDefault(row.dflt_value);
|
|
1545
|
+
const col = {
|
|
1546
|
+
name: row.name,
|
|
1547
|
+
sqlType: normalizeType(row.type),
|
|
1548
|
+
isPrimaryKey: row.pk === 1,
|
|
1549
|
+
isNotNull: row.notnull === 1,
|
|
1550
|
+
isUnique: uniqueColumns.has(row.name),
|
|
1551
|
+
hasDefault,
|
|
1552
|
+
defaultValue
|
|
1553
|
+
};
|
|
1554
|
+
if (fk) {
|
|
1555
|
+
col.referencesTable = fk.referencesTable;
|
|
1556
|
+
col.referencesColumn = fk.referencesColumn;
|
|
1557
|
+
}
|
|
1558
|
+
return col;
|
|
1559
|
+
});
|
|
1560
|
+
return { columns, indexRows };
|
|
1561
|
+
}
|
|
1562
|
+
function introspectIndexes(client, indexRows) {
|
|
1563
|
+
const indexes = [];
|
|
1564
|
+
for (const idx of indexRows) {
|
|
1565
|
+
if (idx.name.startsWith("sqlite_autoindex_"))
|
|
1566
|
+
continue;
|
|
1567
|
+
const colRows = client.query(`PRAGMA index_info('${idx.name}')`).all();
|
|
1568
|
+
const columns = colRows.map((c) => c.name);
|
|
1569
|
+
indexes.push({
|
|
1570
|
+
name: idx.name,
|
|
1571
|
+
columns,
|
|
1572
|
+
unique: idx.unique === 1
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
return indexes;
|
|
1576
|
+
}
|
|
1577
|
+
function introspect(client) {
|
|
1578
|
+
const tableRows = client.query(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '__flint_migrations'`).all();
|
|
1579
|
+
const tables = {};
|
|
1580
|
+
for (const { name } of tableRows) {
|
|
1581
|
+
const { columns, indexRows } = introspectColumns(client, name);
|
|
1582
|
+
const indexes = introspectIndexes(client, indexRows);
|
|
1583
|
+
tables[name] = {
|
|
1584
|
+
name,
|
|
1585
|
+
columns,
|
|
1586
|
+
indexes
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
return {
|
|
1590
|
+
version: 1,
|
|
1591
|
+
tables
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1453
1594
|
export {
|
|
1454
1595
|
text,
|
|
1455
1596
|
table,
|
|
@@ -1469,7 +1610,9 @@ export {
|
|
|
1469
1610
|
isNotNull,
|
|
1470
1611
|
isNotIn,
|
|
1471
1612
|
isIn,
|
|
1613
|
+
introspect,
|
|
1472
1614
|
integer,
|
|
1615
|
+
index,
|
|
1473
1616
|
gte,
|
|
1474
1617
|
gt,
|
|
1475
1618
|
glob,
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flint-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"flint": "./src/cli.ts"
|
|
6
|
+
},
|
|
4
7
|
"files": [
|
|
5
8
|
"dist",
|
|
6
9
|
"API.md",
|
|
@@ -21,10 +24,18 @@
|
|
|
21
24
|
"./expressions": {
|
|
22
25
|
"import": "./dist/entries/expressions.js",
|
|
23
26
|
"types": "./dist/entries/expressions.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./config": {
|
|
29
|
+
"import": "./dist/entries/config.js",
|
|
30
|
+
"types": "./dist/entries/config.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./migration": {
|
|
33
|
+
"import": "./dist/src/migration/index.js",
|
|
34
|
+
"types": "./dist/src/migration/index.d.ts"
|
|
24
35
|
}
|
|
25
36
|
},
|
|
26
37
|
"scripts": {
|
|
27
|
-
"build": "bun build ./src/index.ts ./src/entries/table.ts ./src/entries/expressions.ts --outdir ./dist --target bun && tsc --project tsconfig.build.json",
|
|
38
|
+
"build": "bun build ./src/index.ts ./src/entries/table.ts ./src/entries/expressions.ts ./src/entries/config.ts --outdir ./dist --target bun && tsc --project tsconfig.build.json",
|
|
28
39
|
"typecheck": "tsc --noEmit",
|
|
29
40
|
"lint": "oxlint",
|
|
30
41
|
"format": "oxfmt"
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// flint CLI — migration generation for flint-orm
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
import { statSync, readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import type { AnyTable, TableDef } from './schema/table.js';
|
|
11
|
+
import type { SchemaState } from './migration/types.js';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Config loading
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
interface FlintConfig {
|
|
18
|
+
/** Path to the SQLite database file. */
|
|
19
|
+
url: string;
|
|
20
|
+
/** Path to schema file or directory. */
|
|
21
|
+
schema: string;
|
|
22
|
+
/** Path to migrations directory (default: ./flint). */
|
|
23
|
+
migrations?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function loadConfig(): Promise<FlintConfig> {
|
|
27
|
+
const configPath = resolve(process.cwd(), 'flint.config.ts');
|
|
28
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
29
|
+
const mod = await import(configUrl);
|
|
30
|
+
return mod.default as FlintConfig;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Schema discovery — import table() exports from schema path
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function isTableDef(value: unknown): boolean {
|
|
38
|
+
return (
|
|
39
|
+
value !== null &&
|
|
40
|
+
typeof value === 'object' &&
|
|
41
|
+
'_' in value &&
|
|
42
|
+
typeof (value as Record<string, unknown>)._ === 'object' &&
|
|
43
|
+
typeof ((value as Record<string, Record<string, unknown>>)._ as Record<string, unknown>).name === 'string'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function discoverTables(schemaPath: string): Promise<unknown[]> {
|
|
48
|
+
const abs = isAbsolute(schemaPath) ? schemaPath : resolve(process.cwd(), schemaPath);
|
|
49
|
+
const stat = statSync(abs);
|
|
50
|
+
|
|
51
|
+
if (stat.isFile()) {
|
|
52
|
+
return importTableFile(abs);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (stat.isDirectory()) {
|
|
56
|
+
return importTableFolder(abs);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw new Error(`Schema path does not exist: ${abs}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function importTableFile(filePath: string): Promise<unknown[]> {
|
|
63
|
+
const url = pathToFileURL(filePath).href;
|
|
64
|
+
const mod = await import(url);
|
|
65
|
+
const tables: unknown[] = [];
|
|
66
|
+
|
|
67
|
+
for (const exportValue of Object.values(mod)) {
|
|
68
|
+
if (isTableDef(exportValue)) {
|
|
69
|
+
tables.push(exportValue);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return tables;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function importTableFolder(folderPath: string): Promise<unknown[]> {
|
|
77
|
+
const entries = readdirSync(folderPath).filter((e) => e.endsWith('.ts'));
|
|
78
|
+
const tables: unknown[] = [];
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const filePath = join(folderPath, entry);
|
|
82
|
+
const discovered = await importTableFile(filePath);
|
|
83
|
+
tables.push(...discovered);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return tables;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Commands
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
|
|
94
|
+
const name = typeof args.name === 'string' ? args.name : undefined;
|
|
95
|
+
const preview = args.preview === true;
|
|
96
|
+
|
|
97
|
+
console.log(`🔍 Discovering schema from: ${config.schema}`);
|
|
98
|
+
const tables = await discoverTables(config.schema);
|
|
99
|
+
|
|
100
|
+
if (tables.length === 0) {
|
|
101
|
+
console.error('❌ No table() definitions found in schema path.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`📦 Found ${tables.length} table(s):`);
|
|
106
|
+
for (const t of tables) {
|
|
107
|
+
console.log(` - ${(t as Record<string, Record<string, unknown>>)._?.name ?? 'unknown'}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (preview) {
|
|
111
|
+
// Dynamic import of migration functions
|
|
112
|
+
const { serializeSchema } = await import('./migration/serialize.js');
|
|
113
|
+
const { diffSchemas, emptyState } = await import('./migration/diff.js');
|
|
114
|
+
const { generateSQL } = await import('./migration/sql.js');
|
|
115
|
+
|
|
116
|
+
// Find latest state
|
|
117
|
+
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
118
|
+
let previousState: SchemaState | null = null;
|
|
119
|
+
if (existsSync(migrationsDir)) {
|
|
120
|
+
const folders = readdirSync(migrationsDir)
|
|
121
|
+
.filter((e) => /^\d{10}_/.test(e))
|
|
122
|
+
.sort()
|
|
123
|
+
.reverse();
|
|
124
|
+
for (const folder of folders) {
|
|
125
|
+
const statePath = join(migrationsDir, folder, 'state.json');
|
|
126
|
+
if (existsSync(statePath)) {
|
|
127
|
+
previousState = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!previousState) previousState = emptyState();
|
|
133
|
+
|
|
134
|
+
const currentState = serializeSchema(tables as AnyTable[]);
|
|
135
|
+
const operations = diffSchemas(previousState, currentState);
|
|
136
|
+
|
|
137
|
+
if (operations.length === 0) {
|
|
138
|
+
console.log('\n✅ No changes detected — schema is already up to date.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const sql = generateSQL(operations);
|
|
143
|
+
const previewLabel = name ? `${name}` : 'unnamed';
|
|
144
|
+
console.log(`\n--- Preview: ${previewLabel} ---`);
|
|
145
|
+
console.log(` Operations: ${operations.length}`);
|
|
146
|
+
console.log(` Migrations dir: ${config.migrations}`);
|
|
147
|
+
console.log(`\n--- SQL ---\n${sql}\n`);
|
|
148
|
+
console.log('(dry run, no files written)');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Dynamic import of the generate function
|
|
153
|
+
const { generate } = await import('./migration/generate.js');
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const result = generate(tables as TableDef<any>[], resolve(process.cwd(), (config.migrations ?? './flint') as string), name);
|
|
157
|
+
console.log(`\n✅ Migration generated: ${result.folderName}`);
|
|
158
|
+
console.log(` Operations: ${result.operations.length}`);
|
|
159
|
+
console.log(`\n--- SQL Preview ---\n${result.sql}`);
|
|
160
|
+
} catch (err: unknown) {
|
|
161
|
+
if (err instanceof Error && err.message.includes('No changes detected')) {
|
|
162
|
+
console.log('\n✅ No changes detected — schema is already up to date.');
|
|
163
|
+
} else {
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function cmdMigrate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
|
|
170
|
+
const { Database } = await import('bun:sqlite');
|
|
171
|
+
const { migrate, getMigrationStatus } = await import('./migration/migrate.js');
|
|
172
|
+
|
|
173
|
+
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
174
|
+
const dryRun = args['dry-run'] === true;
|
|
175
|
+
const statusOnly = args.status === true;
|
|
176
|
+
const name = typeof args.name === 'string' ? args.name : undefined;
|
|
177
|
+
|
|
178
|
+
// Open database from config
|
|
179
|
+
const dbUrl = resolve(process.cwd(), config.url);
|
|
180
|
+
const db = new Database(dbUrl);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
if (statusOnly) {
|
|
184
|
+
const status = getMigrationStatus(db, migrationsDir);
|
|
185
|
+
|
|
186
|
+
if (status.applied.length === 0 && status.pending.length === 0) {
|
|
187
|
+
console.log('✅ No migrations found.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (status.applied.length > 0) {
|
|
192
|
+
console.log('\n📋 Applied migrations:');
|
|
193
|
+
for (const m of status.applied) {
|
|
194
|
+
console.log(` ✓ ${m.name} (${m.folderName})`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (status.pending.length > 0) {
|
|
199
|
+
console.log('\n⏳ Pending migrations:');
|
|
200
|
+
for (const m of status.pending) {
|
|
201
|
+
console.log(` ○ ${m.name} (${m.folderName})`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(`\n ${status.applied.length} applied, ${status.pending.length} pending`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const result = await migrate(db, {
|
|
210
|
+
migrationsDir,
|
|
211
|
+
dryRun,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (result.applied.length === 0) {
|
|
215
|
+
console.log('\n✅ No pending migrations — database is up to date.');
|
|
216
|
+
} else {
|
|
217
|
+
console.log(`\n🚀 ${dryRun ? 'Would apply' : 'Applied'} ${result.applied.length} migration(s):`);
|
|
218
|
+
for (const name of result.applied) {
|
|
219
|
+
console.log(` ✓ ${name}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (result.skipped.length > 0 && !dryRun) {
|
|
224
|
+
console.log(`\n⏭ Skipped ${result.skipped.length} already applied migration(s)`);
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
db.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Help
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
function printHelp(): void {
|
|
236
|
+
console.log(`
|
|
237
|
+
flint — migration CLI for flint-orm
|
|
238
|
+
|
|
239
|
+
Usage:
|
|
240
|
+
flint <command> [options]
|
|
241
|
+
|
|
242
|
+
Commands:
|
|
243
|
+
generate Generate a new migration from schema changes
|
|
244
|
+
migrate Apply pending migrations to the database
|
|
245
|
+
|
|
246
|
+
Options for generate:
|
|
247
|
+
--name <name> Migration name (optional)
|
|
248
|
+
--preview Show what would be generated without writing files
|
|
249
|
+
|
|
250
|
+
Options for migrate:
|
|
251
|
+
--status Show applied and pending migrations
|
|
252
|
+
--dry-run Show what would be applied without executing
|
|
253
|
+
--name <name> Apply only the named migration
|
|
254
|
+
|
|
255
|
+
Examples:
|
|
256
|
+
flint generate --name init_schema
|
|
257
|
+
flint generate --preview
|
|
258
|
+
flint migrate
|
|
259
|
+
flint migrate --status
|
|
260
|
+
flint migrate --dry-run
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Main
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
async function main(): Promise<void> {
|
|
269
|
+
const { values, positionals } = parseArgs({
|
|
270
|
+
args: process.argv.slice(2),
|
|
271
|
+
options: {
|
|
272
|
+
name: { type: 'string', short: 'n' },
|
|
273
|
+
preview: { type: 'boolean', short: 'p' },
|
|
274
|
+
help: { type: 'boolean', short: 'h' },
|
|
275
|
+
status: { type: 'boolean', short: 's' },
|
|
276
|
+
'dry-run': { type: 'boolean', short: 'd' },
|
|
277
|
+
},
|
|
278
|
+
allowPositionals: true,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (values.help || positionals.length === 0) {
|
|
282
|
+
printHelp();
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const command = positionals[0];
|
|
287
|
+
const config = await loadConfig();
|
|
288
|
+
|
|
289
|
+
switch (command) {
|
|
290
|
+
case 'generate':
|
|
291
|
+
await cmdGenerate(values, config);
|
|
292
|
+
break;
|
|
293
|
+
case 'migrate':
|
|
294
|
+
await cmdMigrate(values, config);
|
|
295
|
+
break;
|
|
296
|
+
default:
|
|
297
|
+
console.error(`❌ Unknown command: ${command}`);
|
|
298
|
+
printHelp();
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
main().catch((err) => {
|
|
304
|
+
console.error(err);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
});
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { eq, and, or, gt, gte, lt, lte, neq, isIn, isNotIn, isNull, isNotNull, like, glob, between } from '../query/conditions';
|
|
2
|
-
export type { Condition } from '../query/conditions';
|
|
3
|
-
export { sql } from '../flint';
|
|
4
|
-
export type { SQLExpression } from '../flint';
|
|
5
|
-
export { count, countColumn, sum, avg, min, max } from '../query/aggregates';
|