sql-kite 1.0.7 → 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 (88) hide show
  1. package/package.json +14 -4
  2. package/server/db/connections.js +114 -0
  3. package/server/db/meta-db.js +0 -0
  4. package/server/db/user-db.js +0 -0
  5. package/server/index.js +214 -0
  6. package/server/routes/branches.js +484 -0
  7. package/server/routes/compare.js +109 -0
  8. package/server/routes/export.js +201 -0
  9. package/server/routes/import.js +375 -0
  10. package/server/routes/migrations.js +332 -0
  11. package/server/routes/query.js +67 -0
  12. package/server/routes/schema.js +206 -0
  13. package/server/routes/snapshots.js +322 -0
  14. package/server/routes/tables.js +121 -0
  15. package/server/routes/timeline.js +108 -0
  16. package/server/server.js +0 -0
  17. package/src/commands/import-server.js +2 -5
  18. package/src/commands/import.js +5 -9
  19. package/src/commands/start.js +6 -7
  20. package/src/commands/stop.js +7 -2
  21. package/src/utils/paths.js +61 -1
  22. package/studio-out/404/index.html +1 -0
  23. package/studio-out/404.html +1 -0
  24. package/studio-out/__next.__PAGE__.txt +10 -0
  25. package/studio-out/__next._full.txt +20 -0
  26. package/studio-out/__next._head.txt +6 -0
  27. package/studio-out/__next._index.txt +6 -0
  28. package/studio-out/__next._tree.txt +3 -0
  29. package/studio-out/_next/static/LhecVBdPttfi1VZfXA-dL/_buildManifest.js +11 -0
  30. package/studio-out/_next/static/LhecVBdPttfi1VZfXA-dL/_clientMiddlewareManifest.json +1 -0
  31. package/studio-out/_next/static/LhecVBdPttfi1VZfXA-dL/_ssgManifest.js +1 -0
  32. package/studio-out/_next/static/chunks/118fc599da2f27aa.css +2 -0
  33. package/studio-out/_next/static/chunks/240f2fa81d4fb687.js +1 -0
  34. package/studio-out/_next/static/chunks/42c33ca704af9b68.js +1 -0
  35. package/studio-out/_next/static/chunks/99b69e65b599be96.js +5 -0
  36. package/studio-out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  37. package/studio-out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
  38. package/studio-out/_next/static/chunks/b20313408e970968.css +1 -0
  39. package/studio-out/_next/static/chunks/d104f42a7b0c57b2.js +2 -0
  40. package/studio-out/_next/static/chunks/d4aa9be9c80c98d6.js +1 -0
  41. package/studio-out/_next/static/chunks/f2f58a7e93290fbb.js +1 -0
  42. package/studio-out/_next/static/chunks/f547e106c8e2aa8e.js +1 -0
  43. package/studio-out/_next/static/chunks/f5cb054219e2eeb8.js +109 -0
  44. package/studio-out/_next/static/chunks/turbopack-1577480078e795df.js +4 -0
  45. package/studio-out/_not-found/__next._full.txt +15 -0
  46. package/studio-out/_not-found/__next._head.txt +6 -0
  47. package/studio-out/_not-found/__next._index.txt +6 -0
  48. package/studio-out/_not-found/__next._not-found/__PAGE__.txt +5 -0
  49. package/studio-out/_not-found/__next._not-found.txt +4 -0
  50. package/studio-out/_not-found/__next._tree.txt +2 -0
  51. package/studio-out/_not-found/index.html +1 -0
  52. package/studio-out/_not-found/index.txt +15 -0
  53. package/studio-out/favicon.ico +10 -0
  54. package/studio-out/index.html +37 -0
  55. package/studio-out/index.txt +20 -0
  56. package/studio-out/logo.svg +5 -0
  57. package/studio-out/snapshots/__next._full.txt +15 -0
  58. package/studio-out/snapshots/__next._head.txt +6 -0
  59. package/studio-out/snapshots/__next._index.txt +6 -0
  60. package/studio-out/snapshots/__next._tree.txt +2 -0
  61. package/studio-out/snapshots/__next.snapshots/__PAGE__.txt +5 -0
  62. package/studio-out/snapshots/__next.snapshots.txt +4 -0
  63. package/studio-out/snapshots/index.html +1 -0
  64. package/studio-out/snapshots/index.txt +15 -0
  65. package/studio-out/sql/__next._full.txt +15 -0
  66. package/studio-out/sql/__next._head.txt +6 -0
  67. package/studio-out/sql/__next._index.txt +6 -0
  68. package/studio-out/sql/__next._tree.txt +2 -0
  69. package/studio-out/sql/__next.sql/__PAGE__.txt +5 -0
  70. package/studio-out/sql/__next.sql.txt +4 -0
  71. package/studio-out/sql/index.html +1 -0
  72. package/studio-out/sql/index.txt +15 -0
  73. package/studio-out/tables/__next._full.txt +15 -0
  74. package/studio-out/tables/__next._head.txt +6 -0
  75. package/studio-out/tables/__next._index.txt +6 -0
  76. package/studio-out/tables/__next._tree.txt +2 -0
  77. package/studio-out/tables/__next.tables/__PAGE__.txt +5 -0
  78. package/studio-out/tables/__next.tables.txt +4 -0
  79. package/studio-out/tables/index.html +1 -0
  80. package/studio-out/tables/index.txt +15 -0
  81. package/studio-out/timeline/__next._full.txt +15 -0
  82. package/studio-out/timeline/__next._head.txt +6 -0
  83. package/studio-out/timeline/__next._index.txt +6 -0
  84. package/studio-out/timeline/__next._tree.txt +2 -0
  85. package/studio-out/timeline/__next.timeline/__PAGE__.txt +5 -0
  86. package/studio-out/timeline/__next.timeline.txt +4 -0
  87. package/studio-out/timeline/index.html +1 -0
  88. package/studio-out/timeline/index.txt +15 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sql-kite",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "SQL-Kite CLI — Local-first SQLite workspace with branches, migrations and snapshots.",
5
5
  "type": "module",
6
6
  "preferGlobal": true,
@@ -9,14 +9,16 @@
9
9
  },
10
10
  "scripts": {
11
11
  "dev": "node bin/sql-kite.js",
12
- "start": "node bin/sql-kite.js"
12
+ "start": "node bin/sql-kite.js",
13
+ "prepublishOnly": "node scripts/prepare-publish.js"
13
14
  },
14
15
  "author": "D Krishna",
15
16
  "license": "MIT",
16
17
  "repository": {
17
18
  "type": "git",
18
19
  "url": "https://github.com/Ananta-V/sql-kite"
19
- }, "homepage": "https://github.com/Ananta-V/sql-kite#readme",
20
+ },
21
+ "homepage": "https://github.com/Ananta-V/sql-kite#readme",
20
22
  "bugs": {
21
23
  "url": "https://github.com/Ananta-V/sql-kite/issues"
22
24
  },
@@ -36,15 +38,23 @@
36
38
  "files": [
37
39
  "bin/",
38
40
  "src/",
41
+ "server/",
42
+ "studio-out/",
39
43
  "README.md"
40
44
  ],
41
45
  "dependencies": {
46
+ "@fastify/cors": "^10.0.1",
47
+ "@fastify/static": "^9.0.0",
42
48
  "better-sqlite3": "^9.2.2",
43
49
  "chalk": "^5.3.0",
44
50
  "commander": "^12.0.0",
45
- "find-free-port": "^2.0.0",
51
+ "fastify": "^5.7.4",
46
52
  "inquirer": "^9.2.12",
53
+ "nanoid": "^5.0.4",
47
54
  "open": "^10.0.3",
48
55
  "ora": "^8.0.1"
56
+ },
57
+ "overrides": {
58
+ "glob": "^13.0.0"
49
59
  }
50
60
  }
@@ -0,0 +1,114 @@
1
+ import Database from 'better-sqlite3';
2
+ import { join } from 'path';
3
+
4
+ const connections = new Map();
5
+
6
+ /**
7
+ * Get the current active branch for a project
8
+ */
9
+ export function getCurrentBranch(projectPath) {
10
+ try {
11
+ const metaDb = getMetaDb(projectPath);
12
+ const result = metaDb.prepare(`
13
+ SELECT value FROM settings WHERE key = 'current_branch'
14
+ `).get();
15
+
16
+ return result ? result.value : 'main';
17
+ } catch (error) {
18
+ console.error('Error getting current branch:', error.message);
19
+ return 'main'; // Fallback to main
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Get user database for current branch
25
+ */
26
+ export function getUserDb(projectPath, branchName = null) {
27
+ const branch = branchName || getCurrentBranch(projectPath);
28
+ const key = `user-${projectPath}-${branch}`;
29
+
30
+ if (!connections.has(key)) {
31
+ const metaDb = getMetaDb(projectPath);
32
+
33
+ // Get the DB file for this branch
34
+ const branchInfo = metaDb.prepare(`
35
+ SELECT db_file FROM branches WHERE name = ?
36
+ `).get(branch);
37
+
38
+ if (!branchInfo) {
39
+ throw new Error(`Branch "${branch}" does not exist`);
40
+ }
41
+
42
+ const dbPath = join(projectPath, branchInfo.db_file);
43
+ const db = new Database(dbPath);
44
+ db.pragma('journal_mode = WAL');
45
+ connections.set(key, db);
46
+ }
47
+
48
+ return connections.get(key);
49
+ }
50
+
51
+ export function getMetaDb(projectPath) {
52
+ const key = `meta-${projectPath}`;
53
+
54
+ if (!connections.has(key)) {
55
+ const dbPath = join(projectPath, '.studio', 'meta.db');
56
+ const db = new Database(dbPath);
57
+ db.pragma('journal_mode = WAL');
58
+ connections.set(key, db);
59
+ }
60
+
61
+ return connections.get(key);
62
+ }
63
+
64
+ /**
65
+ * Close and remove connection for a specific branch
66
+ * Used when switching branches
67
+ */
68
+ export function closeBranchConnection(projectPath, branchName) {
69
+ const key = `user-${projectPath}-${branchName}`;
70
+
71
+ if (connections.has(key)) {
72
+ const db = connections.get(key);
73
+ db.close();
74
+ connections.delete(key);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Get a read-only user database connection for compare mode
80
+ */
81
+ export function getReadOnlyUserDb(projectPath, branchName) {
82
+ const branch = branchName || getCurrentBranch(projectPath);
83
+ const key = `readonly-${projectPath}-${branch}`;
84
+
85
+ if (!connections.has(key)) {
86
+ const metaDb = getMetaDb(projectPath);
87
+
88
+ const branchInfo = metaDb.prepare(`
89
+ SELECT db_file FROM branches WHERE name = ?
90
+ `).get(branch);
91
+
92
+ if (!branchInfo) {
93
+ throw new Error(`Branch "${branch}" does not exist`);
94
+ }
95
+
96
+ const dbPath = join(projectPath, branchInfo.db_file);
97
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
98
+ db.pragma('query_only = ON');
99
+ db.pragma('busy_timeout = 2000');
100
+ connections.set(key, db);
101
+ }
102
+
103
+ return connections.get(key);
104
+ }
105
+
106
+ export function closeReadOnlyBranchConnection(projectPath, branchName) {
107
+ const key = `readonly-${projectPath}-${branchName}`;
108
+
109
+ if (connections.has(key)) {
110
+ const db = connections.get(key);
111
+ db.close();
112
+ connections.delete(key);
113
+ }
114
+ }
File without changes
File without changes
@@ -0,0 +1,214 @@
1
+ import Fastify from 'fastify';
2
+ import cors from '@fastify/cors';
3
+ import fastifyStatic from '@fastify/static';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath, pathToFileURL } from 'url';
6
+ import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
7
+ import { createRequire } from 'module';
8
+
9
+ import { getUserDb, getMetaDb, getCurrentBranch } from './db/connections.js';
10
+ import tablesRoutes from './routes/tables.js';
11
+ import queryRoutes from './routes/query.js';
12
+ import schemaRoutes from './routes/schema.js';
13
+ import timelineRoutes from './routes/timeline.js';
14
+ import migrationsRoutes from './routes/migrations.js';
15
+ import snapshotsRoutes from './routes/snapshots.js';
16
+ import branchesRoutes from './routes/branches.js';
17
+ import importRoutes from './routes/import.js';
18
+ import compareRoutes from './routes/compare.js';
19
+ import exportRoutes from './routes/export.js';
20
+
21
+ const require = createRequire(import.meta.url);
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+
24
+ // Import meta migration - works in both layouts:
25
+ // monorepo: packages/server/src/ -> ../../cli/src/utils/meta-migration.js
26
+ // npm: sql-kite/server/ -> ../src/utils/meta-migration.js
27
+ let migrateMetaDb;
28
+ const metaMigrationPaths = [
29
+ join(__dirname, '..', 'src', 'utils', 'meta-migration.js'), // npm layout
30
+ join(__dirname, '..', '..', 'cli', 'src', 'utils', 'meta-migration.js') // monorepo
31
+ ];
32
+ for (const p of metaMigrationPaths) {
33
+ if (existsSync(p)) {
34
+ const mod = await import(pathToFileURL(p).href);
35
+ migrateMetaDb = mod.migrateMetaDb;
36
+ break;
37
+ }
38
+ }
39
+ if (!migrateMetaDb) {
40
+ console.error('Could not find meta-migration.js. Tried:', metaMigrationPaths);
41
+ process.exit(1);
42
+ }
43
+
44
+ const PROJECT_NAME = process.env.PROJECT_NAME;
45
+ const PROJECT_PATH = process.env.PROJECT_PATH;
46
+ const PORT = parseInt(process.env.PORT || '3000');
47
+ const IMPORT_MODE = process.env.IMPORT_MODE === 'true';
48
+
49
+ if (!IMPORT_MODE && (!PROJECT_NAME || !PROJECT_PATH)) {
50
+ console.error('Missing required environment variables: PROJECT_NAME, PROJECT_PATH');
51
+ console.error('Or set IMPORT_MODE=true to run in import-only mode');
52
+ process.exit(1);
53
+ }
54
+
55
+ // Run meta database migration before starting server (skip in import mode)
56
+ if (!IMPORT_MODE) {
57
+ const metaDbPath = join(PROJECT_PATH, '.studio', 'meta.db');
58
+ try {
59
+ migrateMetaDb(metaDbPath);
60
+ } catch (error) {
61
+ console.error('Failed to migrate meta database:', error);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ const fastify = Fastify({
67
+ logger: {
68
+ level: 'info'
69
+ }
70
+ });
71
+
72
+ // CORS - Restrict to localhost origins only for security
73
+ // This prevents CSRF-style attacks from malicious websites
74
+ // Uses a function to allow any localhost port dynamically
75
+ await fastify.register(cors, {
76
+ origin: (origin, callback) => {
77
+ // Allow requests with no origin (same-origin, curl, Postman, etc.)
78
+ if (!origin) {
79
+ return callback(null, true);
80
+ }
81
+
82
+ // Allow any localhost or 127.0.0.1 origin (any port)
83
+ const localhostPattern = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
84
+ if (localhostPattern.test(origin)) {
85
+ return callback(null, true);
86
+ }
87
+
88
+ // Block all other origins
89
+ return callback(new Error('CORS not allowed'), false);
90
+ }
91
+ });
92
+
93
+ // Store project info in fastify instance (if not in import mode)
94
+ if (!IMPORT_MODE) {
95
+ fastify.decorate('projectName', PROJECT_NAME);
96
+ fastify.decorate('projectPath', PROJECT_PATH);
97
+ // API always queries 'main' branch only - branches are for Studio development only
98
+ fastify.decorate('getUserDb', () => getUserDb(PROJECT_PATH, 'main'));
99
+ fastify.decorate('getMetaDb', () => getMetaDb(PROJECT_PATH));
100
+ fastify.decorate('getCurrentBranch', () => getCurrentBranch(PROJECT_PATH));
101
+ }
102
+
103
+ // API Routes
104
+ fastify.register(importRoutes, { prefix: '/api/import' });
105
+
106
+ // Project-specific routes (only in project mode)
107
+ if (!IMPORT_MODE) {
108
+ fastify.register(branchesRoutes, { prefix: '/api/branches' });
109
+ fastify.register(tablesRoutes, { prefix: '/api/tables' });
110
+ fastify.register(queryRoutes, { prefix: '/api/query' });
111
+ fastify.register(schemaRoutes, { prefix: '/api/schema' });
112
+ fastify.register(timelineRoutes, { prefix: '/api/timeline' });
113
+ fastify.register(migrationsRoutes, { prefix: '/api/migrations' });
114
+ fastify.register(snapshotsRoutes, { prefix: '/api/snapshots' });
115
+ fastify.register(compareRoutes, { prefix: '/api/compare' });
116
+ fastify.register(exportRoutes, { prefix: '/api/export' });
117
+ }
118
+
119
+ // Project info endpoint (only in project mode)
120
+ if (!IMPORT_MODE) {
121
+ fastify.get('/api/project', async (request, reply) => {
122
+ const configPath = join(PROJECT_PATH, 'config.json');
123
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
124
+ const currentBranch = getCurrentBranch(PROJECT_PATH);
125
+
126
+ return {
127
+ name: PROJECT_NAME,
128
+ path: PROJECT_PATH,
129
+ port: PORT,
130
+ currentBranch,
131
+ ...config
132
+ };
133
+ });
134
+ } else {
135
+ // Import mode - minimal project info
136
+ fastify.get('/api/project', async (request, reply) => {
137
+ return {
138
+ name: 'Import Mode',
139
+ mode: 'import',
140
+ port: PORT
141
+ };
142
+ });
143
+ }
144
+
145
+ // Serve Studio static files - works in both layouts:
146
+ // monorepo: packages/server/src/ -> ../../studio/out
147
+ // npm: sql-kite/server/ -> ../studio-out
148
+ let studioPath = join(__dirname, '..', 'studio-out'); // npm layout
149
+ if (!existsSync(studioPath)) {
150
+ studioPath = join(__dirname, '..', '..', 'studio', 'out'); // monorepo layout
151
+ }
152
+
153
+ console.log('Looking for Studio at:', studioPath);
154
+ console.log('Studio exists:', existsSync(studioPath));
155
+
156
+ if (!existsSync(studioPath)) {
157
+ console.error('\n❌ ERROR: Studio build not found!');
158
+ console.error('Please run: cd packages/studio && npm run build\n');
159
+ process.exit(1);
160
+ }
161
+
162
+ await fastify.register(fastifyStatic, {
163
+ root: studioPath,
164
+ prefix: '/',
165
+ decorateReply: false,
166
+ index: 'index.html'
167
+ });
168
+
169
+ // Fallback to index.html for client-side routing
170
+ fastify.setNotFoundHandler((request, reply) => {
171
+ if (request.url.startsWith('/api')) {
172
+ reply.code(404).send({ error: 'API endpoint not found' });
173
+ } else {
174
+ reply.sendFile('index.html');
175
+ }
176
+ });
177
+
178
+ // Graceful shutdown
179
+ const closeGracefully = async (signal) => {
180
+ console.log(`\nReceived ${signal}, closing gracefully...`);
181
+
182
+ try {
183
+ // Close database connections
184
+ const userDb = getUserDb(PROJECT_PATH);
185
+ const metaDb = getMetaDb(PROJECT_PATH);
186
+ userDb.close();
187
+ metaDb.close();
188
+
189
+ // Remove server info file
190
+ const serverInfoPath = join(PROJECT_PATH, '.studio', 'server.json');
191
+ if (existsSync(serverInfoPath)) {
192
+ unlinkSync(serverInfoPath);
193
+ }
194
+ } catch (e) {
195
+ console.error('Error during shutdown:', e);
196
+ }
197
+
198
+ await fastify.close();
199
+ process.exit(0);
200
+ };
201
+
202
+ process.on('SIGTERM', closeGracefully);
203
+ process.on('SIGINT', closeGracefully);
204
+
205
+ // Start server
206
+ try {
207
+ await fastify.listen({ port: PORT, host: '0.0.0.0' });
208
+ console.log(`\n✓ Server started for project "${PROJECT_NAME}"`);
209
+ console.log(` URL: http://localhost:${PORT}`);
210
+ console.log(` Path: ${PROJECT_PATH}\n`);
211
+ } catch (err) {
212
+ fastify.log.error(err);
213
+ process.exit(1);
214
+ }