flint-orm 0.2.0 → 0.4.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/API.md +208 -211
- package/README.md +341 -0
- package/dist/index.js +288 -255
- package/package.json +46 -11
- package/src/cli.ts +103 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flint-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"flint": "./src/cli.ts"
|
|
6
6
|
},
|
|
@@ -10,42 +10,77 @@
|
|
|
10
10
|
"README.md"
|
|
11
11
|
],
|
|
12
12
|
"type": "module",
|
|
13
|
-
"main": "./dist/index.js",
|
|
13
|
+
"main": "./dist/src/index.js",
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
-
"import": "./dist/index.js",
|
|
17
|
+
"import": "./dist/src/index.js",
|
|
18
18
|
"types": "./dist/index.d.ts"
|
|
19
19
|
},
|
|
20
|
+
"./bun-sqlite": {
|
|
21
|
+
"import": "./dist/src/entries/bun-sqlite.js",
|
|
22
|
+
"types": "./dist/entries/bun-sqlite.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./better-sqlite3": {
|
|
25
|
+
"import": "./dist/src/entries/better-sqlite3.js",
|
|
26
|
+
"types": "./dist/entries/better-sqlite3.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./libsql": {
|
|
29
|
+
"import": "./dist/src/entries/libsql.js",
|
|
30
|
+
"types": "./dist/entries/libsql.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./libsql-web": {
|
|
33
|
+
"import": "./dist/src/entries/libsql-web.js",
|
|
34
|
+
"types": "./dist/entries/libsql-web.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./turso-sync": {
|
|
37
|
+
"import": "./dist/src/entries/turso-sync.js",
|
|
38
|
+
"types": "./dist/entries/turso-sync.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./turso": {
|
|
41
|
+
"import": "./dist/src/entries/turso.js",
|
|
42
|
+
"types": "./dist/entries/turso.d.ts"
|
|
43
|
+
},
|
|
20
44
|
"./table": {
|
|
21
|
-
"import": "./dist/entries/table.js",
|
|
45
|
+
"import": "./dist/src/entries/table.js",
|
|
22
46
|
"types": "./dist/entries/table.d.ts"
|
|
23
47
|
},
|
|
24
48
|
"./expressions": {
|
|
25
|
-
"import": "./dist/entries/expressions.js",
|
|
49
|
+
"import": "./dist/src/entries/expressions.js",
|
|
26
50
|
"types": "./dist/entries/expressions.d.ts"
|
|
27
51
|
},
|
|
28
52
|
"./config": {
|
|
29
|
-
"import": "./dist/entries/config.js",
|
|
53
|
+
"import": "./dist/src/entries/config.js",
|
|
30
54
|
"types": "./dist/entries/config.d.ts"
|
|
31
55
|
},
|
|
32
56
|
"./migration": {
|
|
33
57
|
"import": "./dist/src/migration/index.js",
|
|
34
|
-
"types": "./dist/
|
|
58
|
+
"types": "./dist/migration/index.d.ts"
|
|
35
59
|
}
|
|
36
60
|
},
|
|
37
61
|
"scripts": {
|
|
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",
|
|
62
|
+
"build": "bun build ./src/index.ts ./src/entries/bun-sqlite.ts ./src/entries/better-sqlite3.ts ./src/entries/libsql.ts ./src/entries/libsql-web.ts ./src/entries/turso-sync.ts ./src/entries/turso.ts ./src/entries/table.ts ./src/entries/expressions.ts ./src/entries/config.ts ./src/migration/index.ts --outdir ./dist --target bun && tsc --project tsconfig.build.json",
|
|
39
63
|
"typecheck": "tsc --noEmit",
|
|
40
|
-
"lint": "oxlint",
|
|
41
|
-
"format": "oxfmt"
|
|
64
|
+
"lint": "oxlint --fix",
|
|
65
|
+
"format": "oxfmt --write",
|
|
66
|
+
"check": "oxlint && oxfmt"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@clack/prompts": "1.7.0",
|
|
70
|
+
"@libsql/client": "0.17.4",
|
|
71
|
+
"@tursodatabase/database": "0.6.1",
|
|
72
|
+
"@tursodatabase/sync": "0.6.1",
|
|
73
|
+
"better-sqlite3": "12.11.1",
|
|
74
|
+
"picocolors": "1.1.1"
|
|
42
75
|
},
|
|
43
76
|
"devDependencies": {
|
|
77
|
+
"@types/better-sqlite3": "7.6.13",
|
|
44
78
|
"@types/bun": "1.3.14",
|
|
79
|
+
"@types/node": "26.1.1",
|
|
45
80
|
"oxfmt": "0.57.0",
|
|
46
81
|
"oxlint": "1.72.0"
|
|
47
82
|
},
|
|
48
83
|
"peerDependencies": {
|
|
49
|
-
"typescript": "7.0.
|
|
84
|
+
"typescript": "7.0.2"
|
|
50
85
|
}
|
|
51
86
|
}
|
package/src/cli.ts
CHANGED
|
@@ -3,20 +3,26 @@
|
|
|
3
3
|
// flint CLI — migration generation for flint-orm
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
|
|
6
|
-
import { parseArgs } from 'node:util';
|
|
6
|
+
import { parseArgs, styleText } from 'node:util';
|
|
7
7
|
import { statSync, readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { join, resolve, isAbsolute } from 'node:path';
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
10
|
import type { AnyTable, TableDef } from './schema/table.js';
|
|
11
11
|
import type { SchemaState } from './migration/types.js';
|
|
12
|
+
import { outro, log, cancel, isCancel, select, pc, note } from './cli/ui.js';
|
|
13
|
+
import { CancellationError } from './migration/diff.js';
|
|
14
|
+
import type { RenamePrompt } from './migration/diff.js';
|
|
15
|
+
import type { Executor } from './executor.js';
|
|
12
16
|
|
|
13
17
|
// ---------------------------------------------------------------------------
|
|
14
18
|
// Config loading
|
|
15
19
|
// ---------------------------------------------------------------------------
|
|
16
20
|
|
|
17
21
|
interface FlintConfig {
|
|
18
|
-
/**
|
|
19
|
-
|
|
22
|
+
/** Which SQLite driver to use. */
|
|
23
|
+
driver: string;
|
|
24
|
+
/** Database connection details. */
|
|
25
|
+
database: { url: string; authToken?: string };
|
|
20
26
|
/** Path to schema file or directory. */
|
|
21
27
|
schema: string;
|
|
22
28
|
/** Path to migrations directory (default: ./flint). */
|
|
@@ -93,24 +99,22 @@ async function importTableFolder(folderPath: string): Promise<unknown[]> {
|
|
|
93
99
|
async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
|
|
94
100
|
const name = typeof args.name === 'string' ? args.name : undefined;
|
|
95
101
|
const preview = args.preview === true;
|
|
102
|
+
const promptRename: RenamePrompt = (message, options) => select({ message: pc.bold(message), options }) as Promise<string | symbol>;
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
log.info(`Discovering schema from: ${config.schema}`);
|
|
98
105
|
const tables = await discoverTables(config.schema);
|
|
99
106
|
|
|
100
107
|
if (tables.length === 0) {
|
|
101
|
-
|
|
108
|
+
cancel('No table() definitions found in schema path.');
|
|
102
109
|
process.exit(1);
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
for (const t of tables) {
|
|
107
|
-
console.log(` - ${(t as Record<string, Record<string, unknown>>)._?.name ?? 'unknown'}`);
|
|
108
|
-
}
|
|
112
|
+
log.info(`Found ${tables.length} table(s): ${tables.map((t) => (t as Record<string, Record<string, unknown>>)._?.name ?? 'unknown').join(', ')}`);
|
|
109
113
|
|
|
110
114
|
if (preview) {
|
|
111
115
|
// Dynamic import of migration functions
|
|
112
116
|
const { serializeSchema } = await import('./migration/serialize.js');
|
|
113
|
-
const { diffSchemas, emptyState } = await import('./migration/diff.js');
|
|
117
|
+
const { diffSchemas, emptyState, resolveRenames } = await import('./migration/diff.js');
|
|
114
118
|
const { generateSQL } = await import('./migration/sql.js');
|
|
115
119
|
|
|
116
120
|
// Find latest state
|
|
@@ -132,20 +136,20 @@ async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config:
|
|
|
132
136
|
if (!previousState) previousState = emptyState();
|
|
133
137
|
|
|
134
138
|
const currentState = serializeSchema(tables as AnyTable[]);
|
|
135
|
-
const
|
|
139
|
+
const rawOps = diffSchemas(previousState, currentState);
|
|
136
140
|
|
|
137
|
-
if (
|
|
138
|
-
|
|
141
|
+
if (rawOps.length === 0) {
|
|
142
|
+
outro('Schema is already up to date.');
|
|
139
143
|
return;
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
// Resolve renames interactively
|
|
147
|
+
const operations = await resolveRenames(rawOps, { interactive: true, prompt: promptRename });
|
|
148
|
+
|
|
142
149
|
const sql = generateSQL(operations);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
console.log(` Migrations dir: ${config.migrations}`);
|
|
147
|
-
console.log(`\n--- SQL ---\n${sql}\n`);
|
|
148
|
-
console.log('(dry run, no files written)');
|
|
150
|
+
log.info(`Operations: ${operations.length}`);
|
|
151
|
+
note(sql, 'SQL', { format: (text) => styleText('dim', text) });
|
|
152
|
+
log.info('(dry run, no files written)');
|
|
149
153
|
return;
|
|
150
154
|
}
|
|
151
155
|
|
|
@@ -153,13 +157,18 @@ async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config:
|
|
|
153
157
|
const { generate } = await import('./migration/generate.js');
|
|
154
158
|
|
|
155
159
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
161
|
+
const result = await generate(tables as TableDef<any>[], migrationsDir, {
|
|
162
|
+
name,
|
|
163
|
+
interactive: true,
|
|
164
|
+
prompt: promptRename,
|
|
165
|
+
});
|
|
166
|
+
log.info(`Operations: ${result.operations.length}`);
|
|
167
|
+
note(result.sql, 'SQL', { format: (text) => styleText('dim', text) });
|
|
168
|
+
log.success(`Migration generated: ${migrationsDir}/${result.folderName}`);
|
|
160
169
|
} catch (err: unknown) {
|
|
161
170
|
if (err instanceof Error && err.message.includes('No changes detected')) {
|
|
162
|
-
|
|
171
|
+
outro('Schema is already up to date.');
|
|
163
172
|
} else {
|
|
164
173
|
throw err;
|
|
165
174
|
}
|
|
@@ -167,64 +176,108 @@ async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config:
|
|
|
167
176
|
}
|
|
168
177
|
|
|
169
178
|
async function cmdMigrate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
|
|
170
|
-
const { Database } = await import('bun:sqlite');
|
|
171
179
|
const { migrate, getMigrationStatus } = await import('./migration/migrate.js');
|
|
172
180
|
|
|
173
181
|
const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
|
|
174
182
|
const dryRun = args['dry-run'] === true;
|
|
175
183
|
const statusOnly = args.status === true;
|
|
176
|
-
const name = typeof args.name === 'string' ? args.name : undefined;
|
|
177
184
|
|
|
178
|
-
//
|
|
179
|
-
const
|
|
180
|
-
const
|
|
185
|
+
// Dynamically create executor based on configured driver
|
|
186
|
+
const isLocalDriver = config.driver === 'bun-sqlite' || config.driver === 'better-sqlite3';
|
|
187
|
+
const dbUrl = isLocalDriver ? resolve(process.cwd(), config.database.url) : config.database.url;
|
|
188
|
+
|
|
189
|
+
let executor: Executor;
|
|
190
|
+
|
|
191
|
+
switch (config.driver) {
|
|
192
|
+
case 'bun-sqlite': {
|
|
193
|
+
const { BunSqliteExecutor } = await import('./drivers/bun-sqlite');
|
|
194
|
+
const { Database } = await import('bun:sqlite');
|
|
195
|
+
executor = new BunSqliteExecutor(new Database(dbUrl));
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case 'better-sqlite3': {
|
|
199
|
+
const { BetterSqlite3Executor } = await import('./drivers/better-sqlite3');
|
|
200
|
+
const Database = (await import('better-sqlite3')).default;
|
|
201
|
+
executor = new BetterSqlite3Executor(new Database(dbUrl));
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'libsql': {
|
|
205
|
+
const { LibsqlExecutor } = await import('./drivers/libsql');
|
|
206
|
+
const { createClient: createLibsqlClient } = await import('@libsql/client');
|
|
207
|
+
executor = new LibsqlExecutor(createLibsqlClient({ url: dbUrl, authToken: config.database.authToken }));
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case 'libsql-web': {
|
|
211
|
+
const { LibsqlWebExecutor } = await import('./drivers/libsql-web');
|
|
212
|
+
const { createClient: createLibsqlWebClient } = await import('@libsql/client/web');
|
|
213
|
+
executor = new LibsqlWebExecutor(createLibsqlWebClient({ url: dbUrl, authToken: config.database.authToken }));
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'turso-sync': {
|
|
217
|
+
const { TursoSyncExecutor } = await import('./drivers/turso-sync');
|
|
218
|
+
const { connect } = await import('@tursodatabase/sync');
|
|
219
|
+
const db = await connect({ path: dbUrl, authToken: config.database.authToken });
|
|
220
|
+
executor = new TursoSyncExecutor(db);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'turso': {
|
|
224
|
+
const { TursoExecutor } = await import('./drivers/turso');
|
|
225
|
+
const { connect } = await import('@tursodatabase/database');
|
|
226
|
+
const db = await connect(dbUrl);
|
|
227
|
+
executor = new TursoExecutor(db);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
default:
|
|
231
|
+
cancel(`Unsupported driver: ${config.driver}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
181
234
|
|
|
182
235
|
try {
|
|
183
236
|
if (statusOnly) {
|
|
184
|
-
const status = getMigrationStatus(
|
|
237
|
+
const status = await getMigrationStatus(executor, migrationsDir);
|
|
185
238
|
|
|
186
239
|
if (status.applied.length === 0 && status.pending.length === 0) {
|
|
187
|
-
|
|
240
|
+
outro('No migrations found.');
|
|
188
241
|
return;
|
|
189
242
|
}
|
|
190
243
|
|
|
191
244
|
if (status.applied.length > 0) {
|
|
192
|
-
|
|
245
|
+
log.info('Applied migrations:');
|
|
193
246
|
for (const m of status.applied) {
|
|
194
|
-
|
|
247
|
+
log.success(`${m.name} (${m.folderName})`);
|
|
195
248
|
}
|
|
196
249
|
}
|
|
197
250
|
|
|
198
251
|
if (status.pending.length > 0) {
|
|
199
|
-
|
|
252
|
+
log.warn('Pending migrations:');
|
|
200
253
|
for (const m of status.pending) {
|
|
201
|
-
|
|
254
|
+
log.message(` ○ ${m.name} (${m.folderName})`);
|
|
202
255
|
}
|
|
203
256
|
}
|
|
204
257
|
|
|
205
|
-
|
|
258
|
+
log.info(`${status.applied.length} applied, ${status.pending.length} pending`);
|
|
206
259
|
return;
|
|
207
260
|
}
|
|
208
261
|
|
|
209
|
-
const result = await migrate(
|
|
262
|
+
const result = await migrate(executor, {
|
|
210
263
|
migrationsDir,
|
|
211
264
|
dryRun,
|
|
212
265
|
});
|
|
213
266
|
|
|
214
267
|
if (result.applied.length === 0) {
|
|
215
|
-
|
|
268
|
+
outro('No pending migrations — database is up to date.');
|
|
216
269
|
} else {
|
|
217
|
-
|
|
218
|
-
for (const
|
|
219
|
-
|
|
270
|
+
log.info(`${dryRun ? 'Would apply' : 'Applied'} ${result.applied.length} migration(s):`);
|
|
271
|
+
for (const appliedName of result.applied) {
|
|
272
|
+
log.success(appliedName);
|
|
220
273
|
}
|
|
221
274
|
}
|
|
222
275
|
|
|
223
276
|
if (result.skipped.length > 0 && !dryRun) {
|
|
224
|
-
|
|
277
|
+
log.info(`Skipped ${result.skipped.length} already applied migration(s)`);
|
|
225
278
|
}
|
|
226
279
|
} finally {
|
|
227
|
-
|
|
280
|
+
executor.close();
|
|
228
281
|
}
|
|
229
282
|
}
|
|
230
283
|
|
|
@@ -233,9 +286,7 @@ async function cmdMigrate(args: ReturnType<typeof parseArgs>['values'], config:
|
|
|
233
286
|
// ---------------------------------------------------------------------------
|
|
234
287
|
|
|
235
288
|
function printHelp(): void {
|
|
236
|
-
|
|
237
|
-
flint — migration CLI for flint-orm
|
|
238
|
-
|
|
289
|
+
log.message(`
|
|
239
290
|
Usage:
|
|
240
291
|
flint <command> [options]
|
|
241
292
|
|
|
@@ -294,13 +345,17 @@ async function main(): Promise<void> {
|
|
|
294
345
|
await cmdMigrate(values, config);
|
|
295
346
|
break;
|
|
296
347
|
default:
|
|
297
|
-
|
|
348
|
+
cancel(`Unknown command: ${command}`);
|
|
298
349
|
printHelp();
|
|
299
350
|
process.exit(1);
|
|
300
351
|
}
|
|
301
352
|
}
|
|
302
353
|
|
|
303
354
|
main().catch((err) => {
|
|
304
|
-
|
|
355
|
+
if (isCancel(err) || err instanceof CancellationError) {
|
|
356
|
+
cancel('Operation cancelled.');
|
|
357
|
+
} else {
|
|
358
|
+
cancel(err.message ?? 'An error occurred');
|
|
359
|
+
}
|
|
305
360
|
process.exit(1);
|
|
306
361
|
});
|