spindb 0.19.5 → 0.21.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 (96) hide show
  1. package/README.md +83 -35
  2. package/cli/commands/backup.ts +63 -66
  3. package/cli/commands/clone.ts +33 -8
  4. package/cli/commands/create.ts +222 -110
  5. package/cli/commands/databases.ts +279 -0
  6. package/cli/commands/delete.ts +23 -8
  7. package/cli/commands/engines.ts +78 -3
  8. package/cli/commands/info.ts +16 -4
  9. package/cli/commands/list.ts +5 -1
  10. package/cli/commands/menu/backup-handlers.ts +185 -153
  11. package/cli/commands/menu/container-handlers.ts +25 -19
  12. package/cli/commands/menu/engine-handlers.ts +110 -156
  13. package/cli/commands/menu/index.ts +2 -2
  14. package/cli/commands/menu/shell-handlers.ts +246 -6
  15. package/cli/commands/menu/sql-handlers.ts +16 -10
  16. package/cli/commands/restore.ts +136 -52
  17. package/cli/commands/start.ts +35 -13
  18. package/cli/commands/stop.ts +41 -14
  19. package/cli/constants.ts +1 -0
  20. package/cli/helpers.ts +71 -0
  21. package/cli/index.ts +11 -25
  22. package/cli/ui/prompts.ts +34 -37
  23. package/config/backup-formats.ts +218 -133
  24. package/config/engine-defaults.ts +13 -0
  25. package/config/engines.json +17 -1
  26. package/config/engines.schema.json +1 -1
  27. package/config/paths.ts +99 -2
  28. package/core/backup-restore.ts +2 -2
  29. package/core/base-binary-manager.ts +551 -0
  30. package/core/base-document-binary-manager.ts +504 -0
  31. package/core/base-embedded-binary-manager.ts +538 -0
  32. package/core/base-server-binary-manager.ts +507 -0
  33. package/core/config-manager.ts +5 -0
  34. package/core/container-manager.ts +82 -20
  35. package/core/dependency-manager.ts +2 -0
  36. package/core/fs-error-utils.ts +79 -0
  37. package/core/hostdb-client.ts +34 -4
  38. package/core/hostdb-releases-factory.ts +237 -0
  39. package/core/platform-service.ts +20 -3
  40. package/core/start-with-retry.ts +1 -11
  41. package/core/version-utils.ts +27 -0
  42. package/engines/clickhouse/backup.ts +15 -1
  43. package/engines/clickhouse/binary-manager.ts +59 -292
  44. package/engines/clickhouse/binary-urls.ts +7 -5
  45. package/engines/clickhouse/hostdb-releases.ts +13 -101
  46. package/engines/clickhouse/index.ts +247 -8
  47. package/engines/clickhouse/restore.ts +54 -11
  48. package/engines/clickhouse/version-maps.ts +2 -0
  49. package/engines/duckdb/binary-manager.ts +20 -461
  50. package/engines/duckdb/hostdb-releases.ts +13 -112
  51. package/engines/duckdb/index.ts +93 -33
  52. package/engines/index.ts +4 -0
  53. package/engines/mariadb/binary-manager.ts +22 -377
  54. package/engines/mariadb/binary-urls.ts +12 -6
  55. package/engines/mariadb/hostdb-releases.ts +12 -93
  56. package/engines/mongodb/backup.ts +9 -8
  57. package/engines/mongodb/binary-manager.ts +24 -415
  58. package/engines/mongodb/binary-urls.ts +10 -10
  59. package/engines/mongodb/restore.ts +40 -9
  60. package/engines/mysql/binary-manager.ts +19 -375
  61. package/engines/mysql/binary-urls.ts +12 -6
  62. package/engines/mysql/hostdb-releases.ts +27 -104
  63. package/engines/postgresql/binary-manager.ts +90 -576
  64. package/engines/postgresql/binary-urls.ts +13 -5
  65. package/engines/postgresql/hostdb-releases.ts +12 -84
  66. package/engines/postgresql/index.ts +175 -78
  67. package/engines/postgresql/restore.ts +24 -18
  68. package/engines/qdrant/api-client.ts +61 -0
  69. package/engines/qdrant/backup.ts +165 -0
  70. package/engines/qdrant/binary-manager.ts +43 -0
  71. package/engines/qdrant/binary-urls.ts +115 -0
  72. package/engines/qdrant/cli-utils.ts +45 -0
  73. package/engines/qdrant/hostdb-releases.ts +23 -0
  74. package/engines/qdrant/index.ts +1112 -0
  75. package/engines/qdrant/restore.ts +203 -0
  76. package/engines/qdrant/version-maps.ts +78 -0
  77. package/engines/qdrant/version-validator.ts +128 -0
  78. package/engines/redis/backup.ts +5 -5
  79. package/engines/redis/binary-manager.ts +21 -452
  80. package/engines/redis/binary-urls.ts +12 -6
  81. package/engines/redis/hostdb-releases.ts +12 -83
  82. package/engines/redis/index.ts +365 -10
  83. package/engines/redis/restore.ts +51 -21
  84. package/engines/sqlite/binary-manager.ts +26 -466
  85. package/engines/sqlite/hostdb-releases.ts +13 -110
  86. package/engines/sqlite/index.ts +46 -17
  87. package/engines/valkey/backup.ts +5 -5
  88. package/engines/valkey/binary-manager.ts +22 -454
  89. package/engines/valkey/binary-urls.ts +7 -6
  90. package/engines/valkey/hostdb-releases.ts +12 -91
  91. package/engines/valkey/index.ts +384 -10
  92. package/engines/valkey/restore.ts +51 -22
  93. package/package.json +6 -3
  94. package/types/index.ts +49 -1
  95. package/core/binary-manager.ts +0 -801
  96. package/engines/postgresql/edb-binary-urls.ts +0 -158
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **One CLI for all your local databases.**
9
9
 
10
- SpinDB is a universal database management tool that combines a package manager, a unified API, and native client tooling for 9 different database engines—all from a single command-line interface. No Docker, no VMs, no platform-specific installers. Just databases, running natively on your machine.
10
+ SpinDB is a universal database management tool that combines a package manager, a unified API, and native client tooling for 10 different database engines—all from a single command-line interface. No Docker, no VMs, no platform-specific installers. Just databases, running natively on your machine.
11
11
 
12
12
  ```bash
13
13
  npm install -g spindb
@@ -48,7 +48,7 @@ One consistent interface across SQL databases, document stores, key-value stores
48
48
 
49
49
  ```bash
50
50
  # Same commands work for ANY database
51
- spindb create mydb --engine [postgresql|mysql|mariadb|mongodb|redis|valkey|clickhouse|sqlite|duckdb]
51
+ spindb create mydb --engine [postgresql|mysql|mariadb|mongodb|redis|valkey|clickhouse|sqlite|duckdb|qdrant]
52
52
  spindb start mydb
53
53
  spindb connect mydb
54
54
  spindb backup mydb
@@ -70,7 +70,7 @@ spindb run mydb -c "SELECT * FROM system.tables" # ClickHouse
70
70
 
71
71
  ## Platform Coverage
72
72
 
73
- SpinDB works across **9 database engines** and **5 platform architectures** with a **single, consistent API**.
73
+ SpinDB works across **10 database engines** and **5 platform architectures** with a **single, consistent API**.
74
74
 
75
75
  | Database | macOS ARM64 | macOS Intel | Linux x64 | Linux ARM64 | Windows x64 |
76
76
  |----------|:-----------:|:-----------:|:---------:|:-----------:|:-----------:|
@@ -83,8 +83,9 @@ SpinDB works across **9 database engines** and **5 platform architectures** with
83
83
  | 🔴 **Redis** | ✅ | ✅ | ✅ | ✅ | ✅ |
84
84
  | 🔷 **Valkey** | ✅ | ✅ | ✅ | ✅ | ✅ |
85
85
  | 🏠 **ClickHouse** | ✅ | ✅ | ✅ | ✅ | ❌ |
86
+ | 🧭 **Qdrant** | ✅ | ✅ | ✅ | ✅ | ✅ |
86
87
 
87
- **44 combinations. One CLI. Zero configuration.**
88
+ **49 combinations. One CLI. Zero configuration.**
88
89
 
89
90
  ---
90
91
 
@@ -163,7 +164,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
163
164
  | Feature | SpinDB | Docker | DBngin | Postgres.app | XAMPP |
164
165
  |---------|--------|--------|--------|--------------|-------|
165
166
  | No Docker required | ✅ | ❌ | ✅ | ✅ | ✅ |
166
- | Multiple DB engines | ✅ 9 engines | ✅ Unlimited | ✅ 3 engines | ❌ PostgreSQL only | ⚠️ MySQL only |
167
+ | Multiple DB engines | ✅ 10 engines | ✅ Unlimited | ✅ 3 engines | ❌ PostgreSQL only | ⚠️ MySQL only |
167
168
  | CLI-first | ✅ | ✅ | ❌ GUI-first | ❌ GUI-first | ❌ GUI-first |
168
169
  | Multiple versions | ✅ | ✅ | ✅ | ✅ | ❌ |
169
170
  | Clone databases | ✅ | Manual | ✅ | ❌ | ❌ |
@@ -177,7 +178,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
177
178
 
178
179
  ## Supported Databases
179
180
 
180
- SpinDB supports **9 database engines** with **multiple versions** for each:
181
+ SpinDB supports **10 database engines** with **multiple versions** for each:
181
182
 
182
183
  | Engine | Type | Versions | Default Port | Query Language |
183
184
  |--------|------|----------|--------------|----------------|
@@ -190,10 +191,11 @@ SpinDB supports **9 database engines** with **multiple versions** for each:
190
191
  | 🔴 **Redis** | Key-Value Store | 7, 8 | 6379 | Redis commands |
191
192
  | 🔷 **Valkey** | Key-Value Store | 8, 9 | 6379 | Redis commands |
192
193
  | 🏠 **ClickHouse** | Columnar OLAP | 25.12 | 9000 (TCP), 8123 (HTTP) | SQL (ClickHouse dialect) |
194
+ | 🧭 **Qdrant** | Vector Search | 1 | 6333 (HTTP), 6334 (gRPC) | REST API |
193
195
 
194
196
  ### Engine Categories
195
197
 
196
- **Server-Based Databases** (PostgreSQL, MySQL, MariaDB, MongoDB, Redis, Valkey, ClickHouse):
198
+ **Server-Based Databases** (PostgreSQL, MySQL, MariaDB, MongoDB, Redis, Valkey, ClickHouse, Qdrant):
197
199
  - Start/stop server processes
198
200
  - Bind to localhost ports
199
201
  - Data stored in `~/.spindb/containers/{engine}/{name}/`
@@ -269,8 +271,8 @@ psql $(spindb url mydb)
269
271
  spindb backup mydb # Auto-generated filename
270
272
  spindb backup mydb --name production-backup # Custom name
271
273
  spindb backup mydb --output ./backups/ # Custom directory
272
- spindb backup mydb --format sql # SQL text format
273
- spindb backup mydb --format dump # Binary format
274
+ spindb backup mydb --format sql # SQL text format (PostgreSQL)
275
+ spindb backup mydb --format custom # Custom binary format (PostgreSQL)
274
276
 
275
277
  # Restore from backups
276
278
  spindb restore mydb backup.dump
@@ -307,6 +309,12 @@ spindb edit mydb --relocate ~/new/path # Move SQLite/DuckDB file
307
309
  spindb logs mydb
308
310
  spindb logs mydb --follow # Follow mode (tail -f)
309
311
  spindb logs mydb -n 100 # Last 100 lines
312
+
313
+ # Manage database tracking (for external scripts)
314
+ spindb databases list mydb # List tracked databases
315
+ spindb databases add mydb analytics # Add to tracking
316
+ spindb databases remove mydb old_backup # Remove from tracking
317
+ spindb databases sync mydb oldname newname # Sync after rename
310
318
  ```
311
319
 
312
320
  ### Engine & System Management
@@ -430,7 +438,7 @@ spindb create modern --engine postgresql --db-version 18
430
438
 
431
439
  # Backup formats
432
440
  spindb backup myapp --format sql # Plain SQL (.sql)
433
- spindb backup myapp --format dump # Binary custom format (.dump)
441
+ spindb backup myapp --format custom # Binary custom format (.dump)
434
442
  ```
435
443
 
436
444
  **Versions:** 15, 16, 17, 18
@@ -527,6 +535,23 @@ spindb run warehouse -c "SELECT * FROM system.tables"
527
535
  **Ports:** 9000 (native TCP), 8123 (HTTP)
528
536
  **Tools:** `clickhouse-client`, `clickhouse-server` (included)
529
537
 
538
+ ### Qdrant 🧭
539
+
540
+ ```bash
541
+ # Create Qdrant database (vector similarity search)
542
+ spindb create vectors --engine qdrant
543
+ spindb start vectors
544
+
545
+ # Access via REST API
546
+ curl http://127.0.0.1:6333/collections
547
+ ```
548
+
549
+ **Version:** 1 (1.16.3)
550
+ **Platforms:** macOS, Linux, Windows (all platforms)
551
+ **Ports:** 6333 (REST/HTTP), 6334 (gRPC)
552
+ **Query interface:** REST API (no CLI shell - use curl or API clients)
553
+ **Tools:** `qdrant` (included)
554
+
530
555
  ---
531
556
 
532
557
  ## Enhanced CLI Tools
@@ -544,6 +569,7 @@ SpinDB supports enhanced database shells with auto-completion, syntax highlighti
544
569
  | Redis | `redis-cli` | `iredis` | - |
545
570
  | Valkey | `valkey-cli` | `iredis` (compatible) | - |
546
571
  | ClickHouse | `clickhouse-client` | - | `usql` |
572
+ | Qdrant | REST API | - | - |
547
573
 
548
574
  Install and use in one command:
549
575
 
@@ -563,12 +589,12 @@ Every engine supports backup and restore with engine-specific formats:
563
589
 
564
590
  | Format | Extension | Tool | Use Case |
565
591
  |--------|-----------|------|----------|
566
- | SQL | `.sql` | pg_dump | Human-readable, portable |
567
- | Custom | `.dump` | pg_dump -Fc | Compressed, faster restore |
592
+ | sql | `.sql` | pg_dump | Human-readable, portable |
593
+ | custom | `.dump` | pg_dump -Fc | Compressed, faster restore |
568
594
 
569
595
  ```bash
570
- spindb backup mydb --sql # Plain SQL
571
- spindb backup mydb --dump # Binary custom format
596
+ spindb backup mydb --format sql # Plain SQL
597
+ spindb backup mydb --format custom # Binary custom format
572
598
  spindb restore mydb backup.dump
573
599
  ```
574
600
 
@@ -576,23 +602,23 @@ spindb restore mydb backup.dump
576
602
 
577
603
  | Format | Extension | Tool | Use Case |
578
604
  |--------|-----------|------|----------|
579
- | SQL | `.sql` | mysqldump / mariadb-dump | Human-readable |
580
- | Compressed | `.sql.gz` | mysqldump + gzip | Smaller file size |
605
+ | sql | `.sql` | mysqldump / mariadb-dump | Human-readable |
606
+ | compressed | `.sql.gz` | mysqldump + gzip | Smaller file size |
581
607
 
582
608
  ```bash
583
- spindb backup mydb --sql # Plain SQL
584
- spindb backup mydb --dump # Compressed SQL
609
+ spindb backup mydb --format sql # Plain SQL
610
+ spindb backup mydb --format compressed # Compressed SQL
585
611
  ```
586
612
 
587
613
  ### MongoDB
588
614
 
589
615
  | Format | Extension | Tool | Use Case |
590
616
  |--------|-----------|------|----------|
591
- | BSON | `.bson` | mongodump | Binary, preserves all types |
592
- | Archive | `.archive` | mongodump --archive | Single compressed file |
617
+ | bson | _(directory)_ | mongodump | Binary, preserves all types |
618
+ | archive | `.archive` | mongodump --archive | Single compressed file |
593
619
 
594
620
  ```bash
595
- spindb backup mydb # BSON directory
621
+ spindb backup mydb --format bson # BSON directory
596
622
  spindb backup mydb --format archive # Single .archive file
597
623
  ```
598
624
 
@@ -600,12 +626,12 @@ spindb backup mydb --format archive # Single .archive file
600
626
 
601
627
  | Format | Extension | Tool | Use Case |
602
628
  |--------|-----------|------|----------|
603
- | RDB | `.rdb` | BGSAVE | Binary snapshot, requires restart |
604
- | Text | `.redis` / `.valkey` | Custom | Human-readable commands |
629
+ | rdb | `.rdb` | BGSAVE | Binary snapshot, requires stop/start |
630
+ | text | `.redis` / `.valkey` | Custom | Human-readable commands |
605
631
 
606
632
  ```bash
607
- spindb backup mydb --dump # RDB snapshot
608
- spindb backup mydb --sql # Text commands
633
+ spindb backup mydb --format rdb # RDB snapshot (default)
634
+ spindb backup mydb --format text # Text commands
609
635
 
610
636
  # Restore with merge or replace strategy
611
637
  spindb restore mydb backup.redis # Prompts: Replace all / Merge
@@ -615,23 +641,32 @@ spindb restore mydb backup.redis # Prompts: Replace all / Merge
615
641
 
616
642
  | Format | Extension | Tool | Use Case |
617
643
  |--------|-----------|------|----------|
618
- | SQL | `.sql` | .dump / duckdb | Human-readable |
619
- | Binary | `.sqlite` / `.duckdb` | File copy | Exact database copy |
644
+ | sql | `.sql` | .dump / duckdb | Human-readable |
645
+ | binary | `.sqlite` / `.duckdb` | File copy | Exact database copy |
620
646
 
621
647
  ```bash
622
- spindb backup mydb --sql # SQL dump
623
- spindb backup mydb --dump # Binary copy
648
+ spindb backup mydb --format sql # SQL dump
649
+ spindb backup mydb --format binary # Binary copy (default)
624
650
  ```
625
651
 
626
652
  ### ClickHouse
627
653
 
628
654
  | Format | Extension | Tool | Use Case |
629
655
  |--------|-----------|------|----------|
630
- | SQL | `.sql` | clickhouse-client | Plain SQL dump |
631
- | Native | `.clickhouse` | clickhouse-backup | Native format (future) |
656
+ | sql | `.sql` | clickhouse-client | Plain SQL dump |
657
+
658
+ ```bash
659
+ spindb backup mydb --format sql # SQL dump (only format)
660
+ ```
661
+
662
+ ### Qdrant
663
+
664
+ | Format | Extension | Tool | Use Case |
665
+ |--------|-----------|------|----------|
666
+ | snapshot | `.snapshot` | REST API | Full database snapshot |
632
667
 
633
668
  ```bash
634
- spindb backup mydb --sql # SQL dump
669
+ spindb backup mydb --format snapshot # Snapshot (only format)
635
670
  ```
636
671
 
637
672
  ---
@@ -652,7 +687,7 @@ spindb start staging
652
687
 
653
688
  ### Restore from Remote
654
689
 
655
- Pull production data into local databases:
690
+ Pull production data into local databases. **All engines support remote restore via connection strings:**
656
691
 
657
692
  ```bash
658
693
  # Create new database from remote
@@ -662,6 +697,19 @@ spindb create prod-copy --from "postgresql://user:pass@prod-host:5432/production
662
697
  spindb restore mydb --from-url "postgresql://user:pass@prod-host:5432/production"
663
698
  ```
664
699
 
700
+ **Supported connection string formats:**
701
+
702
+ | Engine | Format | Example |
703
+ |--------|--------|---------|
704
+ | PostgreSQL | `postgresql://` or `postgres://` | `postgresql://user:pass@host:5432/db` |
705
+ | MySQL | `mysql://` | `mysql://root:pass@host:3306/db` |
706
+ | MariaDB | `mysql://` or `mariadb://` | `mariadb://root:pass@host:3307/db` |
707
+ | MongoDB | `mongodb://` or `mongodb+srv://` | `mongodb://user:pass@host:27017/db` |
708
+ | Redis | `redis://` | `redis://:password@host:6379/0` |
709
+ | Valkey | `redis://` | `redis://:password@host:6379/0` |
710
+ | ClickHouse | `clickhouse://` or `http://` | `clickhouse://default:pass@host:8123/db` |
711
+ | Qdrant | `qdrant://` or `http://` | `http://host:6333?api_key=KEY` |
712
+
665
713
  ### Multi-Version Support
666
714
 
667
715
  Run different versions of the same database simultaneously:
@@ -742,7 +790,7 @@ The following engines may be added based on community interest:
742
790
 
743
791
  - **Local only** - Databases bind to `127.0.0.1`. Remote connection support planned for v1.1.
744
792
  - **ClickHouse Windows** - Not supported (hostdb doesn't build for Windows).
745
- - **Redis/Valkey remote dump** - Cannot create containers directly from remote connection strings. Use backup/restore workflow instead.
793
+ - **Qdrant** - Uses REST API instead of CLI shell. Access via HTTP at the configured port.
746
794
 
747
795
  ---
748
796
 
@@ -817,7 +865,7 @@ See [FEATURE.md](FEATURE.md) for adding new database engines.
817
865
 
818
866
  SpinDB is powered by:
819
867
 
820
- - **[hostdb](https://github.com/robertjbass/hostdb)** - Pre-compiled database binaries for 9 engines across all major platforms. Makes Docker-free multi-version database support possible.
868
+ - **[hostdb](https://github.com/robertjbass/hostdb)** - Pre-compiled database binaries for 10 engines across all major platforms. Makes Docker-free multi-version database support possible.
821
869
 
822
870
  ---
823
871
 
@@ -14,6 +14,15 @@ import {
14
14
  import { createSpinner } from '../ui/spinner'
15
15
  import { uiSuccess, uiError, uiWarning, formatBytes } from '../ui/theme'
16
16
  import { getMissingDependencies } from '../../core/dependency-manager'
17
+ import { isFileBasedEngine } from '../../types'
18
+ import {
19
+ getBackupExtension,
20
+ getBackupSpinnerLabel,
21
+ getDefaultFormat,
22
+ isValidFormat,
23
+ getValidFormats,
24
+ } from '../../config/backup-formats'
25
+ import type { BackupFormatType } from '../../types'
17
26
 
18
27
  function generateTimestamp(): string {
19
28
  const now = new Date()
@@ -28,42 +37,6 @@ function generateDefaultFilename(
28
37
  return `${containerName}-${database}-backup-${timestamp}`
29
38
  }
30
39
 
31
- function getExtension(format: 'sql' | 'dump', engine: string): string {
32
- // Handle 'sql' format (human-readable option)
33
- if (format === 'sql') {
34
- // MongoDB uses BSON directory format for 'sql' choice
35
- return engine === 'mongodb' ? '' : '.sql'
36
- }
37
-
38
- // Handle 'dump' format (binary/compressed option)
39
- switch (engine) {
40
- case 'mysql':
41
- return '.sql.gz'
42
- case 'sqlite':
43
- return '.sqlite'
44
- case 'mongodb':
45
- return '.archive'
46
- case 'redis':
47
- return '.rdb'
48
- case 'postgresql':
49
- default:
50
- return '.dump'
51
- }
52
- }
53
-
54
- function getFormatDescription(format: 'sql' | 'dump', engine: string): string {
55
- if (engine === 'redis') {
56
- return 'RDB snapshot'
57
- }
58
- if (engine === 'mongodb') {
59
- return format === 'sql' ? 'BSON directory' : 'archive'
60
- }
61
- if (engine === 'sqlite') {
62
- return format === 'sql' ? 'SQL' : 'binary'
63
- }
64
- return format === 'sql' ? 'SQL' : 'dump'
65
- }
66
-
67
40
  export const backupCommand = new Command('backup')
68
41
  .description('Create a backup of a database')
69
42
  .argument('[container]', 'Container name')
@@ -73,9 +46,7 @@ export const backupCommand = new Command('backup')
73
46
  '-o, --output <path>',
74
47
  'Output directory (defaults to current directory)',
75
48
  )
76
- .option('--format <format>', 'Output format: sql or dump')
77
- .option('--sql', 'Output as plain SQL (shorthand for --format sql)')
78
- .option('--dump', 'Output as dump format (shorthand for --format dump)')
49
+ .option('--format <format>', 'Backup format (engine-specific, e.g., sql, custom, rdb, binary)')
79
50
  .option('-j, --json', 'Output result as JSON')
80
51
  .action(
81
52
  async (
@@ -85,8 +56,6 @@ export const backupCommand = new Command('backup')
85
56
  name?: string
86
57
  output?: string
87
58
  format?: string
88
- sql?: boolean
89
- dump?: boolean
90
59
  json?: boolean
91
60
  },
92
61
  ) => {
@@ -94,6 +63,12 @@ export const backupCommand = new Command('backup')
94
63
  let containerName = containerArg
95
64
 
96
65
  if (!containerName) {
66
+ // JSON mode requires container name argument
67
+ if (options.json) {
68
+ console.log(JSON.stringify({ error: 'Container name is required' }))
69
+ process.exit(1)
70
+ }
71
+
97
72
  const containers = await containerManager.list()
98
73
  const running = containers.filter((c) => c.status === 'running')
99
74
 
@@ -124,22 +99,30 @@ export const backupCommand = new Command('backup')
124
99
 
125
100
  const config = await containerManager.getConfig(containerName)
126
101
  if (!config) {
127
- console.error(uiError(`Container "${containerName}" not found`))
102
+ if (options.json) {
103
+ console.log(JSON.stringify({ error: `Container "${containerName}" not found` }))
104
+ } else {
105
+ console.error(uiError(`Container "${containerName}" not found`))
106
+ }
128
107
  process.exit(1)
129
108
  }
130
109
 
131
110
  const { engine: engineName } = config
132
111
 
133
- const running = await processManager.isRunning(containerName, {
134
- engine: engineName,
135
- })
136
- if (!running) {
137
- console.error(
138
- uiError(
139
- `Container "${containerName}" is not running. Start it first.`,
140
- ),
141
- )
142
- process.exit(1)
112
+ // File-based engines (SQLite, DuckDB) don't need to be "running"
113
+ if (!isFileBasedEngine(engineName)) {
114
+ const running = await processManager.isRunning(containerName, {
115
+ engine: engineName,
116
+ })
117
+ if (!running) {
118
+ const errorMsg = `Container "${containerName}" is not running. Start it first.`
119
+ if (options.json) {
120
+ console.log(JSON.stringify({ error: errorMsg }))
121
+ } else {
122
+ console.error(uiError(errorMsg))
123
+ }
124
+ process.exit(1)
125
+ }
143
126
  }
144
127
 
145
128
  const engine = getEngine(engineName)
@@ -193,20 +176,26 @@ export const backupCommand = new Command('backup')
193
176
  }
194
177
  }
195
178
 
196
- let format: 'sql' | 'dump' = 'sql'
179
+ let format: BackupFormatType = getDefaultFormat(engineName)
197
180
 
198
- if (options.sql) {
199
- format = 'sql'
200
- } else if (options.dump) {
201
- format = 'dump'
202
- } else if (options.format) {
203
- if (options.format !== 'sql' && options.format !== 'dump') {
204
- console.error(uiError('Format must be "sql" or "dump"'))
181
+ if (options.format) {
182
+ if (!isValidFormat(engineName, options.format)) {
183
+ const validFormats = getValidFormats(engineName)
184
+ const errorMsg = `Invalid format "${options.format}" for ${engineName}. Valid formats: ${validFormats.join(', ')}`
185
+ if (options.json) {
186
+ console.log(JSON.stringify({ error: errorMsg }))
187
+ } else {
188
+ console.error(uiError(errorMsg))
189
+ }
205
190
  process.exit(1)
206
191
  }
207
- format = options.format as 'sql' | 'dump'
192
+ // Safe to cast: isValidFormat above guarantees the format is valid
193
+ format = options.format as BackupFormatType
208
194
  } else if (!containerArg) {
209
- format = await promptBackupFormat(engineName)
195
+ const selectedFormat = await promptBackupFormat(engineName)
196
+ if (selectedFormat) {
197
+ format = selectedFormat
198
+ }
210
199
  }
211
200
 
212
201
  const defaultFilename = generateDefaultFilename(
@@ -219,13 +208,13 @@ export const backupCommand = new Command('backup')
219
208
  filename = await promptBackupFilename(defaultFilename)
220
209
  }
221
210
 
222
- const extension = getExtension(format, engineName)
211
+ const extension = getBackupExtension(engineName, format)
223
212
  const outputDir = options.output || process.cwd()
224
213
  const outputPath = join(outputDir, `${filename}${extension}`)
225
214
 
226
- const formatDesc = getFormatDescription(format, engineName)
215
+ const spinnerLabel = getBackupSpinnerLabel(engineName, format)
227
216
  const backupSpinner = createSpinner(
228
- `Creating ${formatDesc} backup of "${databaseName}"...`,
217
+ `Creating ${spinnerLabel} backup of "${databaseName}"...`,
229
218
  )
230
219
  backupSpinner.start()
231
220
 
@@ -269,6 +258,10 @@ export const backupCommand = new Command('backup')
269
258
  )
270
259
 
271
260
  if (matchingPattern) {
261
+ if (options.json) {
262
+ console.log(JSON.stringify({ error: e.message }))
263
+ process.exit(1)
264
+ }
272
265
  const missingTool = matchingPattern.replace(' not found', '')
273
266
  const installed = await promptInstallDependencies(missingTool)
274
267
  if (installed) {
@@ -279,7 +272,11 @@ export const backupCommand = new Command('backup')
279
272
  process.exit(1)
280
273
  }
281
274
 
282
- console.error(uiError(e.message))
275
+ if (options.json) {
276
+ console.log(JSON.stringify({ error: e.message }))
277
+ } else {
278
+ console.error(uiError(e.message))
279
+ }
283
280
  process.exit(1)
284
281
  }
285
282
  },
@@ -23,6 +23,12 @@ export const cloneCommand = new Command('clone')
23
23
  let targetName = target
24
24
 
25
25
  if (!sourceName) {
26
+ // JSON mode requires source container name argument
27
+ if (options.json) {
28
+ console.log(JSON.stringify({ error: 'Source container name is required' }))
29
+ process.exit(1)
30
+ }
31
+
26
32
  const containers = await containerManager.list()
27
33
  const stopped = containers.filter((c) => c.status !== 'running')
28
34
 
@@ -57,7 +63,11 @@ export const cloneCommand = new Command('clone')
57
63
 
58
64
  const sourceConfig = await containerManager.getConfig(sourceName)
59
65
  if (!sourceConfig) {
60
- console.error(uiError(`Container "${sourceName}" not found`))
66
+ if (options.json) {
67
+ console.log(JSON.stringify({ error: `Container "${sourceName}" not found` }))
68
+ } else {
69
+ console.error(uiError(`Container "${sourceName}" not found`))
70
+ }
61
71
  process.exit(1)
62
72
  }
63
73
 
@@ -65,15 +75,21 @@ export const cloneCommand = new Command('clone')
65
75
  engine: sourceConfig.engine,
66
76
  })
67
77
  if (running) {
68
- console.error(
69
- uiError(
70
- `Container "${sourceName}" is running. Stop it first to clone.`,
71
- ),
72
- )
78
+ const errorMsg = `Container "${sourceName}" is running. Stop it first to clone.`
79
+ if (options.json) {
80
+ console.log(JSON.stringify({ error: errorMsg }))
81
+ } else {
82
+ console.error(uiError(errorMsg))
83
+ }
73
84
  process.exit(1)
74
85
  }
75
86
 
76
87
  if (!targetName) {
88
+ // JSON mode requires target container name argument
89
+ if (options.json) {
90
+ console.log(JSON.stringify({ error: 'Target container name is required' }))
91
+ process.exit(1)
92
+ }
77
93
  targetName = await promptContainerName(`${sourceName}-copy`)
78
94
  }
79
95
 
@@ -83,7 +99,12 @@ export const cloneCommand = new Command('clone')
83
99
  engine: sourceConfig.engine,
84
100
  })
85
101
  ) {
86
- console.error(uiError(`Container "${targetName}" already exists`))
102
+ const errorMsg = `Container "${targetName}" already exists`
103
+ if (options.json) {
104
+ console.log(JSON.stringify({ error: errorMsg }))
105
+ } else {
106
+ console.error(uiError(errorMsg))
107
+ }
87
108
  process.exit(1)
88
109
  }
89
110
 
@@ -121,7 +142,11 @@ export const cloneCommand = new Command('clone')
121
142
  }
122
143
  } catch (error) {
123
144
  const e = error as Error
124
- console.error(uiError(e.message))
145
+ if (options.json) {
146
+ console.log(JSON.stringify({ error: e.message }))
147
+ } else {
148
+ console.error(uiError(e.message))
149
+ }
125
150
  process.exit(1)
126
151
  }
127
152
  },