spindb 0.9.1 → 0.9.3

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 (55) hide show
  1. package/README.md +5 -8
  2. package/cli/commands/attach.ts +108 -0
  3. package/cli/commands/backup.ts +13 -11
  4. package/cli/commands/clone.ts +14 -10
  5. package/cli/commands/config.ts +29 -29
  6. package/cli/commands/connect.ts +51 -39
  7. package/cli/commands/create.ts +65 -32
  8. package/cli/commands/delete.ts +8 -8
  9. package/cli/commands/deps.ts +17 -15
  10. package/cli/commands/detach.ts +100 -0
  11. package/cli/commands/doctor.ts +27 -13
  12. package/cli/commands/edit.ts +120 -57
  13. package/cli/commands/engines.ts +17 -15
  14. package/cli/commands/info.ts +8 -6
  15. package/cli/commands/list.ts +127 -18
  16. package/cli/commands/logs.ts +15 -11
  17. package/cli/commands/menu/backup-handlers.ts +52 -47
  18. package/cli/commands/menu/container-handlers.ts +164 -79
  19. package/cli/commands/menu/engine-handlers.ts +21 -11
  20. package/cli/commands/menu/index.ts +4 -4
  21. package/cli/commands/menu/shell-handlers.ts +34 -31
  22. package/cli/commands/menu/sql-handlers.ts +22 -16
  23. package/cli/commands/menu/update-handlers.ts +19 -17
  24. package/cli/commands/restore.ts +22 -20
  25. package/cli/commands/run.ts +20 -18
  26. package/cli/commands/self-update.ts +5 -5
  27. package/cli/commands/sqlite.ts +247 -0
  28. package/cli/commands/start.ts +11 -9
  29. package/cli/commands/stop.ts +9 -9
  30. package/cli/commands/url.ts +12 -9
  31. package/cli/helpers.ts +9 -4
  32. package/cli/index.ts +6 -0
  33. package/cli/ui/prompts.ts +12 -5
  34. package/cli/ui/spinner.ts +4 -4
  35. package/cli/ui/theme.ts +4 -4
  36. package/config/paths.ts +0 -8
  37. package/core/binary-manager.ts +5 -1
  38. package/core/config-manager.ts +32 -0
  39. package/core/container-manager.ts +5 -5
  40. package/core/platform-service.ts +3 -3
  41. package/core/start-with-retry.ts +6 -6
  42. package/core/transaction-manager.ts +6 -6
  43. package/engines/mysql/backup.ts +37 -13
  44. package/engines/mysql/index.ts +11 -11
  45. package/engines/mysql/restore.ts +4 -4
  46. package/engines/mysql/version-validator.ts +2 -2
  47. package/engines/postgresql/binary-manager.ts +17 -17
  48. package/engines/postgresql/index.ts +7 -2
  49. package/engines/postgresql/restore.ts +2 -2
  50. package/engines/postgresql/version-validator.ts +2 -2
  51. package/engines/sqlite/index.ts +30 -15
  52. package/engines/sqlite/registry.ts +64 -33
  53. package/engines/sqlite/scanner.ts +99 -0
  54. package/package.json +4 -3
  55. package/types/index.ts +21 -1
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
  import chalk from 'chalk'
3
- import { header, success, warning, error } from '../ui/theme'
3
+ import { header, uiSuccess, uiWarning, uiError } from '../ui/theme'
4
4
  import { createSpinner } from '../ui/spinner'
5
5
  import {
6
6
  detectPackageManager,
@@ -84,7 +84,7 @@ depsCommand
84
84
  // Check specific engine
85
85
  const engineConfig = getEngineDependencies(options.engine)
86
86
  if (!engineConfig) {
87
- console.error(error(`Unknown engine: ${options.engine}`))
87
+ console.error(uiError(`Unknown engine: ${options.engine}`))
88
88
  console.log(
89
89
  chalk.gray(
90
90
  ` Available engines: ${engineDependencies.map((e) => e.engine).join(', ')}`,
@@ -104,9 +104,9 @@ depsCommand
104
104
  const total = statuses.length
105
105
  console.log()
106
106
  if (installed === total) {
107
- console.log(success(`All ${total} dependencies installed`))
107
+ console.log(uiSuccess(`All ${total} dependencies installed`))
108
108
  } else {
109
- console.log(warning(`${installed}/${total} dependencies installed`))
109
+ console.log(uiWarning(`${installed}/${total} dependencies installed`))
110
110
  console.log()
111
111
  console.log(
112
112
  chalk.gray(` Run: spindb deps install --engine ${options.engine}`),
@@ -132,7 +132,7 @@ depsCommand
132
132
  const packageManager = await detectPackageManager()
133
133
 
134
134
  if (!packageManager) {
135
- console.log(error('No supported package manager detected'))
135
+ console.log(uiError('No supported package manager detected'))
136
136
  console.log()
137
137
 
138
138
  const platform = getCurrentPlatform()
@@ -161,7 +161,7 @@ depsCommand
161
161
  const missing = await getAllMissingDependencies()
162
162
 
163
163
  if (missing.length === 0) {
164
- console.log(success('All dependencies are already installed'))
164
+ console.log(uiSuccess('All dependencies are already installed'))
165
165
  return
166
166
  }
167
167
 
@@ -182,14 +182,14 @@ depsCommand
182
182
  spinner.warn('Some dependencies failed to install')
183
183
  console.log()
184
184
  for (const f of failed) {
185
- console.log(error(` ${f.dependency.name}: ${f.error}`))
185
+ console.log(uiError(` ${f.dependency.name}: ${f.error}`))
186
186
  }
187
187
  }
188
188
 
189
189
  if (succeeded.length > 0) {
190
190
  console.log()
191
191
  console.log(
192
- success(
192
+ uiSuccess(
193
193
  `Installed: ${succeeded.map((r) => r.dependency.name).join(', ')}`,
194
194
  ),
195
195
  )
@@ -198,7 +198,7 @@ depsCommand
198
198
  // Install dependencies for specific engine
199
199
  const engineConfig = getEngineDependencies(options.engine)
200
200
  if (!engineConfig) {
201
- console.error(error(`Unknown engine: ${options.engine}`))
201
+ console.error(uiError(`Unknown engine: ${options.engine}`))
202
202
  console.log(
203
203
  chalk.gray(
204
204
  ` Available engines: ${engineDependencies.map((e) => e.engine).join(', ')}`,
@@ -211,7 +211,9 @@ depsCommand
211
211
 
212
212
  if (missing.length === 0) {
213
213
  console.log(
214
- success(`All ${engineConfig.displayName} dependencies are installed`),
214
+ uiSuccess(
215
+ `All ${engineConfig.displayName} dependencies are installed`,
216
+ ),
215
217
  )
216
218
  return
217
219
  }
@@ -241,7 +243,7 @@ depsCommand
241
243
  spinner.warn('Some dependencies failed to install')
242
244
  console.log()
243
245
  for (const f of failed) {
244
- console.log(error(` ${f.dependency.name}: ${f.error}`))
246
+ console.log(uiError(` ${f.dependency.name}: ${f.error}`))
245
247
  }
246
248
 
247
249
  // Show manual instructions
@@ -259,7 +261,7 @@ depsCommand
259
261
  if (succeeded.length > 0) {
260
262
  console.log()
261
263
  console.log(
262
- success(
264
+ uiSuccess(
263
265
  `Installed: ${succeeded.map((r) => r.dependency.name).join(', ')}`,
264
266
  ),
265
267
  )
@@ -276,7 +278,7 @@ depsCommand
276
278
  const missing = await getMissingDependencies('postgresql')
277
279
 
278
280
  if (missing.length === 0) {
279
- console.log(success('All PostgreSQL dependencies are installed'))
281
+ console.log(uiSuccess('All PostgreSQL dependencies are installed'))
280
282
  return
281
283
  }
282
284
 
@@ -300,14 +302,14 @@ depsCommand
300
302
  spinner.warn('Some dependencies failed to install')
301
303
  console.log()
302
304
  for (const f of failed) {
303
- console.log(error(` ${f.dependency.name}: ${f.error}`))
305
+ console.log(uiError(` ${f.dependency.name}: ${f.error}`))
304
306
  }
305
307
  }
306
308
 
307
309
  if (succeeded.length > 0) {
308
310
  console.log()
309
311
  console.log(
310
- success(
312
+ uiSuccess(
311
313
  `Installed: ${succeeded.map((r) => r.dependency.name).join(', ')}`,
312
314
  ),
313
315
  )
@@ -0,0 +1,100 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+ import { sqliteRegistry } from '../../engines/sqlite/registry'
4
+ import { containerManager } from '../../core/container-manager'
5
+ import { promptConfirm } from '../ui/prompts'
6
+ import { uiSuccess, uiError, uiWarning } from '../ui/theme'
7
+ import { Engine } from '../../types'
8
+
9
+ export const detachCommand = new Command('detach')
10
+ .description('Unregister a SQLite database from SpinDB (keeps file on disk)')
11
+ .argument('<name>', 'Container name')
12
+ .option('-f, --force', 'Skip confirmation prompt')
13
+ .option('--json', 'Output as JSON')
14
+ .action(
15
+ async (
16
+ name: string,
17
+ options: { force?: boolean; json?: boolean },
18
+ ): Promise<void> => {
19
+ try {
20
+ // Get container config
21
+ const config = await containerManager.getConfig(name)
22
+
23
+ if (!config) {
24
+ if (options.json) {
25
+ console.log(
26
+ JSON.stringify({ success: false, error: 'Container not found' }),
27
+ )
28
+ } else {
29
+ console.error(uiError(`Container "${name}" not found`))
30
+ }
31
+ process.exit(1)
32
+ }
33
+
34
+ // Verify it's a SQLite container
35
+ if (config.engine !== Engine.SQLite) {
36
+ if (options.json) {
37
+ console.log(
38
+ JSON.stringify({
39
+ success: false,
40
+ error:
41
+ 'Not a SQLite container. Use "spindb delete" for server databases.',
42
+ }),
43
+ )
44
+ } else {
45
+ console.error(uiError(`"${name}" is not a SQLite container`))
46
+ console.log(
47
+ chalk.gray(
48
+ ' Use "spindb delete" for server databases (PostgreSQL, MySQL)',
49
+ ),
50
+ )
51
+ }
52
+ process.exit(1)
53
+ }
54
+
55
+ // Confirm unless --force
56
+ if (!options.force && !options.json) {
57
+ const confirmed = await promptConfirm(
58
+ `Detach "${name}" from SpinDB? (file will be kept on disk)`,
59
+ true,
60
+ )
61
+ if (!confirmed) {
62
+ console.log(uiWarning('Cancelled'))
63
+ return
64
+ }
65
+ }
66
+
67
+ const entry = await sqliteRegistry.get(name)
68
+ const filePath = entry?.filePath
69
+
70
+ // Remove from registry only (not the file)
71
+ await sqliteRegistry.remove(name)
72
+
73
+ if (options.json) {
74
+ console.log(
75
+ JSON.stringify({
76
+ success: true,
77
+ name,
78
+ filePath,
79
+ }),
80
+ )
81
+ } else {
82
+ console.log(uiSuccess(`Detached "${name}" from SpinDB`))
83
+ if (filePath) {
84
+ console.log(chalk.gray(` File remains at: ${filePath}`))
85
+ }
86
+ console.log()
87
+ console.log(chalk.gray(' Re-attach with:'))
88
+ console.log(chalk.cyan(` spindb attach ${filePath || '<path>'}`))
89
+ }
90
+ } catch (error) {
91
+ const e = error as Error
92
+ if (options.json) {
93
+ console.log(JSON.stringify({ success: false, error: e.message }))
94
+ } else {
95
+ console.error(uiError(e.message))
96
+ }
97
+ process.exit(1)
98
+ }
99
+ },
100
+ )
@@ -18,7 +18,7 @@ import { sqliteRegistry } from '../../engines/sqlite/registry'
18
18
  import { paths } from '../../config/paths'
19
19
  import { getSupportedEngines } from '../../config/engine-defaults'
20
20
  import { checkEngineDependencies } from '../../core/dependency-manager'
21
- import { header, success } from '../ui/theme'
21
+ import { header, uiSuccess } from '../ui/theme'
22
22
  import { Engine } from '../../types'
23
23
 
24
24
  type HealthCheckResult = {
@@ -61,7 +61,7 @@ async function checkConfiguration(): Promise<HealthCheckResult> {
61
61
  label: 'Refresh binary cache',
62
62
  handler: async () => {
63
63
  await configManager.refreshAllBinaries()
64
- console.log(success('Binary cache refreshed'))
64
+ console.log(uiSuccess('Binary cache refreshed'))
65
65
  },
66
66
  },
67
67
  }
@@ -73,12 +73,12 @@ async function checkConfiguration(): Promise<HealthCheckResult> {
73
73
  message: 'Configuration valid',
74
74
  details: [`Binary tools cached: ${binaryCount}`],
75
75
  }
76
- } catch (err) {
76
+ } catch (error) {
77
77
  return {
78
78
  name: 'Configuration',
79
79
  status: 'error',
80
80
  message: 'Configuration file is corrupted',
81
- details: [(err as Error).message],
81
+ details: [(error as Error).message],
82
82
  }
83
83
  }
84
84
  }
@@ -125,12 +125,12 @@ async function checkContainers(): Promise<HealthCheckResult> {
125
125
  message: `${containers.length} container(s)`,
126
126
  details,
127
127
  }
128
- } catch (err) {
128
+ } catch (error) {
129
129
  return {
130
130
  name: 'Containers',
131
131
  status: 'error',
132
132
  message: 'Failed to list containers',
133
- details: [(err as Error).message],
133
+ details: [(error as Error).message],
134
134
  }
135
135
  }
136
136
  }
@@ -141,8 +141,9 @@ async function checkContainers(): Promise<HealthCheckResult> {
141
141
  async function checkSqliteRegistry(): Promise<HealthCheckResult> {
142
142
  try {
143
143
  const entries = await sqliteRegistry.list()
144
+ const ignoredFolders = await sqliteRegistry.listIgnoredFolders()
144
145
 
145
- if (entries.length === 0) {
146
+ if (entries.length === 0 && ignoredFolders.length === 0) {
146
147
  return {
147
148
  name: 'SQLite Registry',
148
149
  status: 'ok',
@@ -153,32 +154,45 @@ async function checkSqliteRegistry(): Promise<HealthCheckResult> {
153
154
  const orphans = await sqliteRegistry.findOrphans()
154
155
 
155
156
  if (orphans.length > 0) {
157
+ const details = [
158
+ ...orphans.map((o) => `"${o.name}" → ${o.filePath}`),
159
+ ...(ignoredFolders.length > 0
160
+ ? [`${ignoredFolders.length} folder(s) ignored`]
161
+ : []),
162
+ ]
163
+
156
164
  return {
157
165
  name: 'SQLite Registry',
158
166
  status: 'warning',
159
167
  message: `${orphans.length} orphaned entr${orphans.length === 1 ? 'y' : 'ies'} found`,
160
- details: orphans.map((o) => `"${o.name}" → ${o.filePath}`),
168
+ details,
161
169
  action: {
162
170
  label: 'Remove orphaned entries from registry',
163
171
  handler: async () => {
164
172
  const count = await sqliteRegistry.removeOrphans()
165
- console.log(success(`Removed ${count} orphaned entries`))
173
+ console.log(uiSuccess(`Removed ${count} orphaned entries`))
166
174
  },
167
175
  },
168
176
  }
169
177
  }
170
178
 
179
+ const details = [`${entries.length} database(s) registered, all files exist`]
180
+ if (ignoredFolders.length > 0) {
181
+ details.push(`${ignoredFolders.length} folder(s) ignored`)
182
+ }
183
+
171
184
  return {
172
185
  name: 'SQLite Registry',
173
186
  status: 'ok',
174
187
  message: `${entries.length} database(s) registered, all files exist`,
188
+ details: ignoredFolders.length > 0 ? details : undefined,
175
189
  }
176
- } catch (err) {
190
+ } catch (error) {
177
191
  return {
178
192
  name: 'SQLite Registry',
179
193
  status: 'warning',
180
194
  message: 'Could not check registry',
181
- details: [(err as Error).message],
195
+ details: [(error as Error).message],
182
196
  }
183
197
  }
184
198
  }
@@ -211,12 +225,12 @@ async function checkBinaries(): Promise<HealthCheckResult> {
211
225
  message: hasWarning ? 'Some tools missing' : 'All tools available',
212
226
  details: results,
213
227
  }
214
- } catch (err) {
228
+ } catch (error) {
215
229
  return {
216
230
  name: 'Database Tools',
217
231
  status: 'error',
218
232
  message: 'Failed to check tools',
219
- details: [(err as Error).message],
233
+ details: [(error as Error).message],
220
234
  }
221
235
  }
222
236
  }