flint-orm 0.3.0 → 0.4.1

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 (5) hide show
  1. package/API.md +158 -317
  2. package/README.md +239 -134
  3. package/dist/index.js +288 -255
  4. package/package.json +43 -12
  5. package/src/cli.ts +0 -314
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "flint-orm",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "bin": {
5
- "flint": "./src/cli.ts"
5
+ "flint": "./dist/src/cli.js"
6
6
  },
7
7
  "files": [
8
8
  "dist",
@@ -10,46 +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/src/migration/index.d.ts"
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 ./src/cli.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"
42
67
  },
43
68
  "dependencies": {
44
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",
45
74
  "picocolors": "1.1.1"
46
75
  },
47
76
  "devDependencies": {
77
+ "@types/better-sqlite3": "7.6.13",
48
78
  "@types/bun": "1.3.14",
79
+ "@types/node": "26.1.1",
49
80
  "oxfmt": "0.57.0",
50
81
  "oxlint": "1.72.0"
51
82
  },
52
83
  "peerDependencies": {
53
- "typescript": "7.0.1-rc"
84
+ "typescript": "7.0.2"
54
85
  }
55
86
  }
package/src/cli.ts DELETED
@@ -1,314 +0,0 @@
1
- #!/usr/bin/env bun
2
- // ---------------------------------------------------------------------------
3
- // flint CLI — migration generation for flint-orm
4
- // ---------------------------------------------------------------------------
5
-
6
- import { parseArgs, styleText } 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
- import { intro, 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
-
16
- // ---------------------------------------------------------------------------
17
- // Config loading
18
- // ---------------------------------------------------------------------------
19
-
20
- interface FlintConfig {
21
- /** Path to the SQLite database file. */
22
- url: string;
23
- /** Path to schema file or directory. */
24
- schema: string;
25
- /** Path to migrations directory (default: ./flint). */
26
- migrations?: string;
27
- }
28
-
29
- async function loadConfig(): Promise<FlintConfig> {
30
- const configPath = resolve(process.cwd(), 'flint.config.ts');
31
- const configUrl = pathToFileURL(configPath).href;
32
- const mod = await import(configUrl);
33
- return mod.default as FlintConfig;
34
- }
35
-
36
- // ---------------------------------------------------------------------------
37
- // Schema discovery — import table() exports from schema path
38
- // ---------------------------------------------------------------------------
39
-
40
- function isTableDef(value: unknown): boolean {
41
- return (
42
- value !== null &&
43
- typeof value === 'object' &&
44
- '_' in value &&
45
- typeof (value as Record<string, unknown>)._ === 'object' &&
46
- typeof ((value as Record<string, Record<string, unknown>>)._ as Record<string, unknown>).name === 'string'
47
- );
48
- }
49
-
50
- async function discoverTables(schemaPath: string): Promise<unknown[]> {
51
- const abs = isAbsolute(schemaPath) ? schemaPath : resolve(process.cwd(), schemaPath);
52
- const stat = statSync(abs);
53
-
54
- if (stat.isFile()) {
55
- return importTableFile(abs);
56
- }
57
-
58
- if (stat.isDirectory()) {
59
- return importTableFolder(abs);
60
- }
61
-
62
- throw new Error(`Schema path does not exist: ${abs}`);
63
- }
64
-
65
- async function importTableFile(filePath: string): Promise<unknown[]> {
66
- const url = pathToFileURL(filePath).href;
67
- const mod = await import(url);
68
- const tables: unknown[] = [];
69
-
70
- for (const exportValue of Object.values(mod)) {
71
- if (isTableDef(exportValue)) {
72
- tables.push(exportValue);
73
- }
74
- }
75
-
76
- return tables;
77
- }
78
-
79
- async function importTableFolder(folderPath: string): Promise<unknown[]> {
80
- const entries = readdirSync(folderPath).filter((e) => e.endsWith('.ts'));
81
- const tables: unknown[] = [];
82
-
83
- for (const entry of entries) {
84
- const filePath = join(folderPath, entry);
85
- const discovered = await importTableFile(filePath);
86
- tables.push(...discovered);
87
- }
88
-
89
- return tables;
90
- }
91
-
92
- // ---------------------------------------------------------------------------
93
- // Commands
94
- // ---------------------------------------------------------------------------
95
-
96
- async function cmdGenerate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
97
- const name = typeof args.name === 'string' ? args.name : undefined;
98
- const preview = args.preview === true;
99
- const promptRename: RenamePrompt = (message, options) => select({ message: pc.bold(message), options }) as Promise<string | symbol>;
100
-
101
- log.info(`Discovering schema from: ${config.schema}`);
102
- const tables = await discoverTables(config.schema);
103
-
104
- if (tables.length === 0) {
105
- cancel('No table() definitions found in schema path.');
106
- process.exit(1);
107
- }
108
-
109
- log.info(`Found ${tables.length} table(s): ${tables.map((t) => (t as Record<string, Record<string, unknown>>)._?.name ?? 'unknown').join(', ')}`);
110
-
111
- if (preview) {
112
- // Dynamic import of migration functions
113
- const { serializeSchema } = await import('./migration/serialize.js');
114
- const { diffSchemas, emptyState, resolveRenames } = await import('./migration/diff.js');
115
- const { generateSQL } = await import('./migration/sql.js');
116
-
117
- // Find latest state
118
- const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
119
- let previousState: SchemaState | null = null;
120
- if (existsSync(migrationsDir)) {
121
- const folders = readdirSync(migrationsDir)
122
- .filter((e) => /^\d{10}_/.test(e))
123
- .sort()
124
- .reverse();
125
- for (const folder of folders) {
126
- const statePath = join(migrationsDir, folder, 'state.json');
127
- if (existsSync(statePath)) {
128
- previousState = JSON.parse(readFileSync(statePath, 'utf-8'));
129
- break;
130
- }
131
- }
132
- }
133
- if (!previousState) previousState = emptyState();
134
-
135
- const currentState = serializeSchema(tables as AnyTable[]);
136
- const rawOps = diffSchemas(previousState, currentState);
137
-
138
- if (rawOps.length === 0) {
139
- outro('Schema is already up to date.');
140
- return;
141
- }
142
-
143
- // Resolve renames interactively
144
- const operations = await resolveRenames(rawOps, { interactive: true, prompt: promptRename });
145
-
146
- const sql = generateSQL(operations);
147
- log.info(`Operations: ${operations.length}`);
148
- note(sql, 'SQL', { format: (text) => styleText('dim', text) });
149
- log.info('(dry run, no files written)');
150
- return;
151
- }
152
-
153
- // Dynamic import of the generate function
154
- const { generate } = await import('./migration/generate.js');
155
-
156
- try {
157
- const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
158
- const result = await generate(tables as TableDef<any>[], migrationsDir, {
159
- name,
160
- interactive: true,
161
- prompt: promptRename,
162
- });
163
- log.info(`Operations: ${result.operations.length}`);
164
- note(result.sql, 'SQL', { format: (text) => styleText('dim', text) });
165
- log.success(`Migration generated: ${migrationsDir}/${result.folderName}`);
166
- } catch (err: unknown) {
167
- if (err instanceof Error && err.message.includes('No changes detected')) {
168
- outro('Schema is already up to date.');
169
- } else {
170
- throw err;
171
- }
172
- }
173
- }
174
-
175
- async function cmdMigrate(args: ReturnType<typeof parseArgs>['values'], config: FlintConfig): Promise<void> {
176
- const { Database } = await import('bun:sqlite');
177
- const { migrate, getMigrationStatus } = await import('./migration/migrate.js');
178
-
179
- const migrationsDir = resolve(process.cwd(), config.migrations ?? './flint');
180
- const dryRun = args['dry-run'] === true;
181
- const statusOnly = args.status === true;
182
- const name = typeof args.name === 'string' ? args.name : undefined;
183
-
184
- // Open database from config
185
- const dbUrl = resolve(process.cwd(), config.url);
186
- const db = new Database(dbUrl);
187
-
188
- try {
189
- if (statusOnly) {
190
- const status = getMigrationStatus(db, migrationsDir);
191
-
192
- if (status.applied.length === 0 && status.pending.length === 0) {
193
- outro('No migrations found.');
194
- return;
195
- }
196
-
197
- if (status.applied.length > 0) {
198
- log.info('Applied migrations:');
199
- for (const m of status.applied) {
200
- log.success(`${m.name} (${m.folderName})`);
201
- }
202
- }
203
-
204
- if (status.pending.length > 0) {
205
- log.warn('Pending migrations:');
206
- for (const m of status.pending) {
207
- log.message(` ○ ${m.name} (${m.folderName})`);
208
- }
209
- }
210
-
211
- log.info(`${status.applied.length} applied, ${status.pending.length} pending`);
212
- return;
213
- }
214
-
215
- const result = await migrate(db, {
216
- migrationsDir,
217
- dryRun,
218
- });
219
-
220
- if (result.applied.length === 0) {
221
- outro('No pending migrations — database is up to date.');
222
- } else {
223
- log.info(`${dryRun ? 'Would apply' : 'Applied'} ${result.applied.length} migration(s):`);
224
- for (const appliedName of result.applied) {
225
- log.success(appliedName);
226
- }
227
- }
228
-
229
- if (result.skipped.length > 0 && !dryRun) {
230
- log.info(`Skipped ${result.skipped.length} already applied migration(s)`);
231
- }
232
- } finally {
233
- db.close();
234
- }
235
- }
236
-
237
- // ---------------------------------------------------------------------------
238
- // Help
239
- // ---------------------------------------------------------------------------
240
-
241
- function printHelp(): void {
242
- log.message(`
243
- Usage:
244
- flint <command> [options]
245
-
246
- Commands:
247
- generate Generate a new migration from schema changes
248
- migrate Apply pending migrations to the database
249
-
250
- Options for generate:
251
- --name <name> Migration name (optional)
252
- --preview Show what would be generated without writing files
253
-
254
- Options for migrate:
255
- --status Show applied and pending migrations
256
- --dry-run Show what would be applied without executing
257
- --name <name> Apply only the named migration
258
-
259
- Examples:
260
- flint generate --name init_schema
261
- flint generate --preview
262
- flint migrate
263
- flint migrate --status
264
- flint migrate --dry-run
265
- `);
266
- }
267
-
268
- // ---------------------------------------------------------------------------
269
- // Main
270
- // ---------------------------------------------------------------------------
271
-
272
- async function main(): Promise<void> {
273
- const { values, positionals } = parseArgs({
274
- args: process.argv.slice(2),
275
- options: {
276
- name: { type: 'string', short: 'n' },
277
- preview: { type: 'boolean', short: 'p' },
278
- help: { type: 'boolean', short: 'h' },
279
- status: { type: 'boolean', short: 's' },
280
- 'dry-run': { type: 'boolean', short: 'd' },
281
- },
282
- allowPositionals: true,
283
- });
284
-
285
- if (values.help || positionals.length === 0) {
286
- printHelp();
287
- process.exit(0);
288
- }
289
-
290
- const command = positionals[0];
291
- const config = await loadConfig();
292
-
293
- switch (command) {
294
- case 'generate':
295
- await cmdGenerate(values, config);
296
- break;
297
- case 'migrate':
298
- await cmdMigrate(values, config);
299
- break;
300
- default:
301
- cancel(`Unknown command: ${command}`);
302
- printHelp();
303
- process.exit(1);
304
- }
305
- }
306
-
307
- main().catch((err) => {
308
- if (isCancel(err) || err instanceof CancellationError) {
309
- cancel('Operation cancelled.');
310
- } else {
311
- cancel(err.message ?? 'An error occurred');
312
- }
313
- process.exit(1);
314
- });