spindb 0.23.5 → 0.24.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/README.md +13 -9
- package/cli/commands/engines.ts +76 -1
- package/cli/commands/menu/backup-handlers.ts +9 -0
- package/cli/commands/menu/container-handlers.ts +52 -32
- package/cli/commands/menu/shell-handlers.ts +58 -3
- package/cli/commands/menu/sql-handlers.ts +2 -1
- package/cli/constants.ts +2 -0
- package/cli/helpers.ts +68 -0
- package/cli/ui/prompts.ts +3 -2
- package/config/backup-formats.ts +14 -0
- package/config/engine-defaults.ts +13 -0
- package/config/engines.json +16 -0
- package/core/config-manager.ts +5 -0
- package/core/dependency-manager.ts +2 -0
- package/engines/couchdb/api-client.ts +81 -0
- package/engines/couchdb/backup.ts +137 -0
- package/engines/couchdb/binary-manager.ts +84 -0
- package/engines/couchdb/binary-urls.ts +115 -0
- package/engines/couchdb/hostdb-releases.ts +23 -0
- package/engines/couchdb/index.ts +1147 -0
- package/engines/couchdb/restore.ts +289 -0
- package/engines/couchdb/version-maps.ts +78 -0
- package/engines/couchdb/version-validator.ts +111 -0
- package/engines/index.ts +4 -0
- package/package.json +2 -1
- package/types/index.ts +9 -0
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
|
|
10
|
+
SpinDB is a universal database management tool that combines a package manager, a unified API, and native client tooling for 13 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|ferretdb|redis|valkey|clickhouse|sqlite|duckdb|qdrant|meilisearch]
|
|
51
|
+
spindb create mydb --engine [postgresql|mysql|mariadb|mongodb|ferretdb|redis|valkey|clickhouse|sqlite|duckdb|qdrant|meilisearch|couchdb]
|
|
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 **
|
|
73
|
+
SpinDB works across **13 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
|
|----------|:-----------:|:-----------:|:---------:|:-----------:|:-----------:|
|
|
@@ -86,8 +86,9 @@ SpinDB works across **12 database engines** and **5 platform architectures** wit
|
|
|
86
86
|
| 🏠 **ClickHouse** | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
87
87
|
| 🧭 **Qdrant** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
88
88
|
| 🔍 **Meilisearch** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
89
|
+
| 🛋 **CouchDB** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
89
90
|
|
|
90
|
-
**
|
|
91
|
+
**63 combinations. One CLI. Zero configuration.**
|
|
91
92
|
|
|
92
93
|
---
|
|
93
94
|
|
|
@@ -166,7 +167,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
|
|
|
166
167
|
| Feature | SpinDB | Docker | DBngin | Postgres.app | XAMPP |
|
|
167
168
|
|---------|--------|--------|--------|--------------|-------|
|
|
168
169
|
| No Docker required | ✅ | ❌ | ✅ | ✅ | ✅ |
|
|
169
|
-
| Multiple DB engines | ✅
|
|
170
|
+
| Multiple DB engines | ✅ 13 engines | ✅ Unlimited | ✅ 3 engines | ❌ PostgreSQL only | ⚠️ MySQL only |
|
|
170
171
|
| CLI-first | ✅ | ✅ | ❌ GUI-first | ❌ GUI-first | ❌ GUI-first |
|
|
171
172
|
| Multiple versions | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
172
173
|
| Clone databases | ✅ | Manual | ✅ | ❌ | ❌ |
|
|
@@ -180,7 +181,7 @@ SpinDB runs databases as **native processes** with **isolated data directories**
|
|
|
180
181
|
|
|
181
182
|
## Supported Databases
|
|
182
183
|
|
|
183
|
-
SpinDB supports **
|
|
184
|
+
SpinDB supports **13 database engines** with **multiple versions** for each:
|
|
184
185
|
|
|
185
186
|
| Engine | Type | Versions | Default Port | Query Language |
|
|
186
187
|
|--------|------|----------|--------------|----------------|
|
|
@@ -196,10 +197,11 @@ SpinDB supports **12 database engines** with **multiple versions** for each:
|
|
|
196
197
|
| 🏠 **ClickHouse** | Columnar OLAP | 25.12 | 9000 (TCP), 8123 (HTTP) | SQL (ClickHouse dialect) |
|
|
197
198
|
| 🧭 **Qdrant** | Vector Search | 1 | 6333 (HTTP), 6334 (gRPC) | REST API |
|
|
198
199
|
| 🔍 **Meilisearch** | Full-Text Search | 1 | 7700 | REST API |
|
|
200
|
+
| 🛋 **CouchDB** | Document Store | 3 | 5984 | REST API |
|
|
199
201
|
|
|
200
202
|
### Engine Categories
|
|
201
203
|
|
|
202
|
-
**Server-Based Databases** (PostgreSQL, MySQL, MariaDB, MongoDB, FerretDB, Redis, Valkey, ClickHouse, Qdrant, Meilisearch):
|
|
204
|
+
**Server-Based Databases** (PostgreSQL, MySQL, MariaDB, MongoDB, FerretDB, Redis, Valkey, ClickHouse, Qdrant, Meilisearch, CouchDB):
|
|
203
205
|
- Start/stop server processes
|
|
204
206
|
- Bind to localhost ports
|
|
205
207
|
- Data stored in `~/.spindb/containers/{engine}/{name}/`
|
|
@@ -601,6 +603,7 @@ SpinDB supports enhanced database shells with auto-completion, syntax highlighti
|
|
|
601
603
|
| ClickHouse | `clickhouse-client` | - | `usql` |
|
|
602
604
|
| Qdrant | REST API | - | - |
|
|
603
605
|
| Meilisearch | REST API | - | - |
|
|
606
|
+
| CouchDB | REST API | - | - |
|
|
604
607
|
|
|
605
608
|
Install and use in one command:
|
|
606
609
|
|
|
@@ -751,6 +754,7 @@ spindb restore mydb --from-url "postgresql://user:pass@prod-host:5432/production
|
|
|
751
754
|
| ClickHouse | `clickhouse://` or `http://` | `clickhouse://default:pass@host:8123/db` |
|
|
752
755
|
| Qdrant | `qdrant://` or `http://` | `http://host:6333?api_key=KEY` |
|
|
753
756
|
| Meilisearch | `meilisearch://` or `http://` | `http://host:7700?api_key=KEY` |
|
|
757
|
+
| CouchDB | `couchdb://` or `http://` | `http://user:pass@host:5984/db` |
|
|
754
758
|
|
|
755
759
|
### Multi-Version Support
|
|
756
760
|
|
|
@@ -831,7 +835,7 @@ The following engines may be added based on community interest:
|
|
|
831
835
|
- **Local only** - Databases bind to `127.0.0.1`. Remote connection support planned for v1.1.
|
|
832
836
|
- **ClickHouse Windows** - Not supported (hostdb doesn't build for Windows).
|
|
833
837
|
- **FerretDB Windows** - Not supported (postgresql-documentdb has startup issues on Windows).
|
|
834
|
-
- **Qdrant &
|
|
838
|
+
- **Qdrant, Meilisearch & CouchDB** - Use REST API instead of CLI shell. Access via HTTP at the configured port.
|
|
835
839
|
|
|
836
840
|
---
|
|
837
841
|
|
|
@@ -906,7 +910,7 @@ See [FEATURE.md](FEATURE.md) for adding new database engines.
|
|
|
906
910
|
|
|
907
911
|
SpinDB is powered by:
|
|
908
912
|
|
|
909
|
-
- **[hostdb](https://github.com/robertjbass/hostdb)** - Pre-compiled database binaries for
|
|
913
|
+
- **[hostdb](https://github.com/robertjbass/hostdb)** - Pre-compiled database binaries for 14 engines across all major platforms. Makes Docker-free multi-version database support possible.
|
|
910
914
|
|
|
911
915
|
---
|
|
912
916
|
|
package/cli/commands/engines.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
type InstalledValkeyEngine,
|
|
44
44
|
type InstalledQdrantEngine,
|
|
45
45
|
type InstalledMeilisearchEngine,
|
|
46
|
+
type InstalledCouchDBEngine,
|
|
46
47
|
} from '../helpers'
|
|
47
48
|
import { Engine, Platform } from '../../types'
|
|
48
49
|
import {
|
|
@@ -60,6 +61,7 @@ import { clickhouseBinaryManager } from '../../engines/clickhouse/binary-manager
|
|
|
60
61
|
import { qdrantBinaryManager } from '../../engines/qdrant/binary-manager'
|
|
61
62
|
import { meilisearchBinaryManager } from '../../engines/meilisearch/binary-manager'
|
|
62
63
|
import { ferretdbBinaryManager } from '../../engines/ferretdb/binary-manager'
|
|
64
|
+
import { couchdbBinaryManager } from '../../engines/couchdb/binary-manager'
|
|
63
65
|
import {
|
|
64
66
|
DEFAULT_DOCUMENTDB_VERSION,
|
|
65
67
|
normalizeDocumentDBVersion,
|
|
@@ -457,6 +459,9 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
457
459
|
const meilisearchEngines = engines.filter(
|
|
458
460
|
(e): e is InstalledMeilisearchEngine => e.engine === 'meilisearch',
|
|
459
461
|
)
|
|
462
|
+
const couchdbEngines = engines.filter(
|
|
463
|
+
(e): e is InstalledCouchDBEngine => e.engine === 'couchdb',
|
|
464
|
+
)
|
|
460
465
|
|
|
461
466
|
// Calculate total size for PostgreSQL
|
|
462
467
|
const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
|
|
@@ -621,6 +626,21 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
621
626
|
)
|
|
622
627
|
}
|
|
623
628
|
|
|
629
|
+
// CouchDB rows
|
|
630
|
+
for (const engine of couchdbEngines) {
|
|
631
|
+
const icon = ENGINE_ICONS.couchdb
|
|
632
|
+
const platformInfo = `${engine.platform}-${engine.arch}`
|
|
633
|
+
const engineDisplay = `${icon} couchdb`
|
|
634
|
+
|
|
635
|
+
console.log(
|
|
636
|
+
chalk.gray(' ') +
|
|
637
|
+
chalk.cyan(padWithEmoji(engineDisplay, 13)) +
|
|
638
|
+
chalk.yellow(engine.version.padEnd(12)) +
|
|
639
|
+
chalk.gray(platformInfo.padEnd(18)) +
|
|
640
|
+
chalk.white(formatBytes(engine.sizeBytes)),
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
|
|
624
644
|
console.log(chalk.gray(' ' + '─'.repeat(55)))
|
|
625
645
|
|
|
626
646
|
// Summary
|
|
@@ -716,6 +736,17 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
|
|
|
716
736
|
),
|
|
717
737
|
)
|
|
718
738
|
}
|
|
739
|
+
if (couchdbEngines.length > 0) {
|
|
740
|
+
const totalCouchDBSize = couchdbEngines.reduce(
|
|
741
|
+
(acc, e) => acc + e.sizeBytes,
|
|
742
|
+
0,
|
|
743
|
+
)
|
|
744
|
+
console.log(
|
|
745
|
+
chalk.gray(
|
|
746
|
+
` CouchDB: ${couchdbEngines.length} version(s), ${formatBytes(totalCouchDBSize)}`,
|
|
747
|
+
),
|
|
748
|
+
)
|
|
749
|
+
}
|
|
719
750
|
console.log()
|
|
720
751
|
}
|
|
721
752
|
|
|
@@ -1601,9 +1632,53 @@ enginesCommand
|
|
|
1601
1632
|
return
|
|
1602
1633
|
}
|
|
1603
1634
|
|
|
1635
|
+
if (['couchdb', 'couch'].includes(normalizedEngine)) {
|
|
1636
|
+
if (!version) {
|
|
1637
|
+
console.error(uiError('CouchDB requires a version (e.g., 3)'))
|
|
1638
|
+
process.exit(1)
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
const engine = getEngine(Engine.CouchDB)
|
|
1642
|
+
|
|
1643
|
+
const spinner = createSpinner(`Checking CouchDB ${version} binaries...`)
|
|
1644
|
+
spinner.start()
|
|
1645
|
+
|
|
1646
|
+
let wasCached = false
|
|
1647
|
+
await engine.ensureBinaries(version, ({ stage, message }) => {
|
|
1648
|
+
if (stage === 'cached') {
|
|
1649
|
+
wasCached = true
|
|
1650
|
+
spinner.text = `CouchDB ${version} binaries ready (cached)`
|
|
1651
|
+
} else {
|
|
1652
|
+
spinner.text = message
|
|
1653
|
+
}
|
|
1654
|
+
})
|
|
1655
|
+
|
|
1656
|
+
if (wasCached) {
|
|
1657
|
+
spinner.succeed(`CouchDB ${version} binaries already installed`)
|
|
1658
|
+
} else {
|
|
1659
|
+
spinner.succeed(`CouchDB ${version} binaries downloaded`)
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// Show the path for reference
|
|
1663
|
+
const { platform: couchdbPlatform, arch: couchdbArch } =
|
|
1664
|
+
platformService.getPlatformInfo()
|
|
1665
|
+
const couchdbFullVersion = couchdbBinaryManager.getFullVersion(version)
|
|
1666
|
+
const binPath = paths.getBinaryPath({
|
|
1667
|
+
engine: 'couchdb',
|
|
1668
|
+
version: couchdbFullVersion,
|
|
1669
|
+
platform: couchdbPlatform,
|
|
1670
|
+
arch: couchdbArch,
|
|
1671
|
+
})
|
|
1672
|
+
console.log(chalk.gray(` Location: ${binPath}`))
|
|
1673
|
+
|
|
1674
|
+
// Skip client tools check for CouchDB - it's a REST API server
|
|
1675
|
+
// with no CLI client tools (uses HTTP protocols instead)
|
|
1676
|
+
return
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1604
1679
|
console.error(
|
|
1605
1680
|
uiError(
|
|
1606
|
-
`Unknown engine "${engineName}". Supported: postgresql, mysql, mariadb, sqlite, duckdb, mongodb, ferretdb, redis, valkey, clickhouse, qdrant, meilisearch`,
|
|
1681
|
+
`Unknown engine "${engineName}". Supported: postgresql, mysql, mariadb, sqlite, duckdb, mongodb, ferretdb, redis, valkey, clickhouse, qdrant, meilisearch, couchdb`,
|
|
1607
1682
|
),
|
|
1608
1683
|
)
|
|
1609
1684
|
process.exit(1)
|
|
@@ -165,6 +165,15 @@ function validateConnectionString(
|
|
|
165
165
|
return 'Connection string must start with meilisearch://, http://, or https://'
|
|
166
166
|
}
|
|
167
167
|
break
|
|
168
|
+
case Engine.CouchDB:
|
|
169
|
+
if (
|
|
170
|
+
!input.startsWith('couchdb://') &&
|
|
171
|
+
!input.startsWith('http://') &&
|
|
172
|
+
!input.startsWith('https://')
|
|
173
|
+
) {
|
|
174
|
+
return 'Connection string must start with couchdb://, http://, or https://'
|
|
175
|
+
}
|
|
176
|
+
break
|
|
168
177
|
case Engine.SQLite:
|
|
169
178
|
case Engine.DuckDB:
|
|
170
179
|
return 'File-based engines do not support remote connection strings'
|
|
@@ -151,19 +151,29 @@ export async function handleCreate(): Promise<'main' | void> {
|
|
|
151
151
|
)
|
|
152
152
|
binarySpinner.start()
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
try {
|
|
155
|
+
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
156
|
+
if (isInstalled) {
|
|
157
|
+
binarySpinner.succeed(
|
|
158
|
+
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
159
|
+
)
|
|
160
|
+
} else {
|
|
161
|
+
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
162
|
+
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
163
|
+
binarySpinner.text = message
|
|
164
|
+
})
|
|
165
|
+
binarySpinner.succeed(
|
|
166
|
+
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
binarySpinner.fail(`Failed to download ${dbEngine.displayName} binaries`)
|
|
171
|
+
const e = error as Error
|
|
172
|
+
console.log()
|
|
173
|
+
console.log(uiError(e.message))
|
|
174
|
+
console.log()
|
|
175
|
+
await pressEnterToContinue()
|
|
176
|
+
return
|
|
167
177
|
}
|
|
168
178
|
}
|
|
169
179
|
|
|
@@ -221,19 +231,29 @@ export async function handleCreate(): Promise<'main' | void> {
|
|
|
221
231
|
)
|
|
222
232
|
binarySpinner.start()
|
|
223
233
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
try {
|
|
235
|
+
const isInstalled = await dbEngine.isBinaryInstalled(version)
|
|
236
|
+
if (isInstalled) {
|
|
237
|
+
binarySpinner.succeed(
|
|
238
|
+
`${dbEngine.displayName} ${version} binaries ready (cached)`,
|
|
239
|
+
)
|
|
240
|
+
} else {
|
|
241
|
+
binarySpinner.text = `Downloading ${dbEngine.displayName} ${version} binaries...`
|
|
242
|
+
await dbEngine.ensureBinaries(version, ({ message }) => {
|
|
243
|
+
binarySpinner.text = message
|
|
244
|
+
})
|
|
245
|
+
binarySpinner.succeed(
|
|
246
|
+
`${dbEngine.displayName} ${version} binaries downloaded`,
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
binarySpinner.fail(`Failed to download ${dbEngine.displayName} binaries`)
|
|
251
|
+
const e = error as Error
|
|
252
|
+
console.log()
|
|
253
|
+
console.log(uiError(e.message))
|
|
254
|
+
console.log()
|
|
255
|
+
await pressEnterToContinue()
|
|
256
|
+
return
|
|
237
257
|
}
|
|
238
258
|
}
|
|
239
259
|
|
|
@@ -335,7 +355,7 @@ export async function handleCreate(): Promise<'main' | void> {
|
|
|
335
355
|
// For other engines (MySQL, SQLite), allow creating a database named 'postgres'
|
|
336
356
|
if (
|
|
337
357
|
config &&
|
|
338
|
-
!(config.engine ===
|
|
358
|
+
!(config.engine === Engine.PostgreSQL && database === 'postgres')
|
|
339
359
|
) {
|
|
340
360
|
const dbSpinner = createSpinner(`Creating database "${database}"...`)
|
|
341
361
|
dbSpinner.start()
|
|
@@ -625,14 +645,14 @@ export async function showContainerSubmenu(
|
|
|
625
645
|
})
|
|
626
646
|
|
|
627
647
|
// Run SQL/script - always enabled for file-based DBs (if file exists), server databases need to be running
|
|
628
|
-
//
|
|
629
|
-
if (config.engine !==
|
|
648
|
+
// REST API engines (Qdrant, Meilisearch, CouchDB) don't support script files - hide the option entirely
|
|
649
|
+
if (config.engine !== Engine.Qdrant && config.engine !== Engine.Meilisearch && config.engine !== Engine.CouchDB) {
|
|
630
650
|
const canRunSql = isFileBasedDB ? existsSync(config.database) : isRunning
|
|
631
|
-
// Engine-specific terminology: Redis/Valkey use commands, MongoDB
|
|
651
|
+
// Engine-specific terminology: Redis/Valkey use commands, MongoDB/FerretDB use scripts, others use SQL
|
|
632
652
|
const runScriptLabel =
|
|
633
|
-
config.engine ===
|
|
653
|
+
config.engine === Engine.Redis || config.engine === Engine.Valkey
|
|
634
654
|
? 'Run command file'
|
|
635
|
-
: config.engine ===
|
|
655
|
+
: config.engine === Engine.MongoDB || config.engine === Engine.FerretDB
|
|
636
656
|
? 'Run script file'
|
|
637
657
|
: 'Run SQL file'
|
|
638
658
|
actionChoices.push({
|
|
@@ -208,6 +208,13 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
208
208
|
engineSpecificInstalled = false
|
|
209
209
|
engineSpecificValue = null
|
|
210
210
|
engineSpecificInstallValue = null
|
|
211
|
+
} else if (config.engine === 'couchdb') {
|
|
212
|
+
// CouchDB uses REST API, open Fauxton dashboard in browser
|
|
213
|
+
defaultShellName = 'Fauxton Dashboard'
|
|
214
|
+
engineSpecificCli = null
|
|
215
|
+
engineSpecificInstalled = false
|
|
216
|
+
engineSpecificValue = null
|
|
217
|
+
engineSpecificInstallValue = null
|
|
211
218
|
} else {
|
|
212
219
|
defaultShellName = 'psql'
|
|
213
220
|
engineSpecificCli = 'pgcli'
|
|
@@ -259,8 +266,19 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
259
266
|
name: `ℹ Show API info`,
|
|
260
267
|
value: 'api-info',
|
|
261
268
|
})
|
|
269
|
+
} else if (config.engine === 'couchdb') {
|
|
270
|
+
// CouchDB: Fauxton dashboard is built-in at /_utils
|
|
271
|
+
choices.push({
|
|
272
|
+
name: `◎ Open Fauxton Dashboard in browser`,
|
|
273
|
+
value: 'default',
|
|
274
|
+
})
|
|
275
|
+
// Always show API info option for CouchDB
|
|
276
|
+
choices.push({
|
|
277
|
+
name: `ℹ Show API info`,
|
|
278
|
+
value: 'api-info',
|
|
279
|
+
})
|
|
262
280
|
} else {
|
|
263
|
-
// Non-Qdrant/Meilisearch engines: show default shell option
|
|
281
|
+
// Non-Qdrant/Meilisearch/CouchDB engines: show default shell option
|
|
264
282
|
choices.push({
|
|
265
283
|
name: `>_ Use default shell (${defaultShellName})`,
|
|
266
284
|
value: 'default',
|
|
@@ -291,14 +309,15 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
291
309
|
}
|
|
292
310
|
}
|
|
293
311
|
|
|
294
|
-
// usql supports SQL databases (PostgreSQL, MySQL, SQLite) - skip for Redis, Valkey, MongoDB, FerretDB, Qdrant, and
|
|
312
|
+
// usql supports SQL databases (PostgreSQL, MySQL, SQLite) - skip for Redis, Valkey, MongoDB, FerretDB, Qdrant, Meilisearch, and CouchDB
|
|
295
313
|
const isNonSqlEngine =
|
|
296
314
|
config.engine === 'redis' ||
|
|
297
315
|
config.engine === 'valkey' ||
|
|
298
316
|
config.engine === 'mongodb' ||
|
|
299
317
|
config.engine === 'ferretdb' ||
|
|
300
318
|
config.engine === 'qdrant' ||
|
|
301
|
-
config.engine === 'meilisearch'
|
|
319
|
+
config.engine === 'meilisearch' ||
|
|
320
|
+
config.engine === 'couchdb'
|
|
302
321
|
if (!isNonSqlEngine) {
|
|
303
322
|
if (usqlInstalled) {
|
|
304
323
|
choices.push({
|
|
@@ -368,6 +387,19 @@ export async function handleOpenShell(containerName: string): Promise<void> {
|
|
|
368
387
|
console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/indexes`))
|
|
369
388
|
console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/health`))
|
|
370
389
|
console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/stats`))
|
|
390
|
+
} else if (config.engine === 'couchdb') {
|
|
391
|
+
console.log(chalk.cyan('CouchDB REST API:'))
|
|
392
|
+
console.log(chalk.white(` HTTP: http://127.0.0.1:${config.port}`))
|
|
393
|
+
console.log(chalk.white(` Fauxton: http://127.0.0.1:${config.port}/_utils`))
|
|
394
|
+
console.log()
|
|
395
|
+
console.log(chalk.cyan('Credentials:'))
|
|
396
|
+
console.log(chalk.white(` Username: admin`))
|
|
397
|
+
console.log(chalk.white(` Password: admin`))
|
|
398
|
+
console.log()
|
|
399
|
+
console.log(chalk.gray('Example curl commands:'))
|
|
400
|
+
console.log(chalk.gray(` curl http://127.0.0.1:${config.port}`))
|
|
401
|
+
console.log(chalk.gray(` curl http://127.0.0.1:${config.port}/_all_dbs`))
|
|
402
|
+
console.log(chalk.gray(` curl -X PUT http://127.0.0.1:${config.port}/mydb`))
|
|
371
403
|
}
|
|
372
404
|
console.log()
|
|
373
405
|
await pressEnterToContinue()
|
|
@@ -814,6 +846,29 @@ async function launchShell(
|
|
|
814
846
|
openInBrowser(dashboardUrl)
|
|
815
847
|
await pressEnterToContinue()
|
|
816
848
|
return
|
|
849
|
+
} else if (config.engine === 'couchdb') {
|
|
850
|
+
// CouchDB: Open Fauxton dashboard in browser (served at /_utils)
|
|
851
|
+
const dashboardUrl = `http://127.0.0.1:${config.port}/_utils`
|
|
852
|
+
console.log()
|
|
853
|
+
console.log(chalk.cyan('CouchDB Fauxton Dashboard'))
|
|
854
|
+
console.log(chalk.gray(` ${dashboardUrl}`))
|
|
855
|
+
console.log()
|
|
856
|
+
console.log(chalk.cyan('Credentials (if prompted):'))
|
|
857
|
+
console.log(chalk.white(` Username: admin`))
|
|
858
|
+
console.log(chalk.white(` Password: admin`))
|
|
859
|
+
console.log()
|
|
860
|
+
|
|
861
|
+
// Prompt before opening so user can see credentials
|
|
862
|
+
await escapeablePrompt([
|
|
863
|
+
{
|
|
864
|
+
type: 'input',
|
|
865
|
+
name: 'continue',
|
|
866
|
+
message: chalk.gray('Press Enter to open in browser...'),
|
|
867
|
+
},
|
|
868
|
+
])
|
|
869
|
+
|
|
870
|
+
openInBrowser(dashboardUrl)
|
|
871
|
+
return
|
|
817
872
|
} else {
|
|
818
873
|
shellCmd = 'psql'
|
|
819
874
|
shellArgs = [connectionString]
|
|
@@ -71,11 +71,12 @@ export async function handleRunSql(containerName: string): Promise<void> {
|
|
|
71
71
|
|
|
72
72
|
// Document/search engines use "Script" terminology
|
|
73
73
|
// MongoDB and FerretDB use JavaScript via mongosh
|
|
74
|
-
// Qdrant and
|
|
74
|
+
// Qdrant, Meilisearch, and CouchDB use REST API (JSON)
|
|
75
75
|
case Engine.MongoDB:
|
|
76
76
|
case Engine.FerretDB:
|
|
77
77
|
case Engine.Qdrant:
|
|
78
78
|
case Engine.Meilisearch:
|
|
79
|
+
case Engine.CouchDB:
|
|
79
80
|
return { type: 'Script', lower: 'script' }
|
|
80
81
|
|
|
81
82
|
// SQL engines use "SQL" terminology
|
package/cli/constants.ts
CHANGED
|
@@ -12,6 +12,7 @@ export const ENGINE_ICONS: Record<string, string> = {
|
|
|
12
12
|
clickhouse: '🏠',
|
|
13
13
|
qdrant: '🧭',
|
|
14
14
|
meilisearch: '🔍',
|
|
15
|
+
couchdb: '🛋',
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// Visual width of each icon in terminal columns
|
|
@@ -30,6 +31,7 @@ export const ENGINE_ICON_WIDTHS: Record<string, number> = {
|
|
|
30
31
|
clickhouse: 2,
|
|
31
32
|
qdrant: 2,
|
|
32
33
|
meilisearch: 2,
|
|
34
|
+
couchdb: 1, // 🛋 couch renders narrow
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export const DEFAULT_ENGINE_ICON = '▣'
|
package/cli/helpers.ts
CHANGED
|
@@ -184,6 +184,16 @@ export type InstalledFerretDBEngine = {
|
|
|
184
184
|
source: 'downloaded'
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
export type InstalledCouchDBEngine = {
|
|
188
|
+
engine: 'couchdb'
|
|
189
|
+
version: string
|
|
190
|
+
platform: string
|
|
191
|
+
arch: string
|
|
192
|
+
path: string
|
|
193
|
+
sizeBytes: number
|
|
194
|
+
source: 'downloaded'
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
export type InstalledEngine =
|
|
188
198
|
| InstalledPostgresEngine
|
|
189
199
|
| InstalledMariadbEngine
|
|
@@ -197,6 +207,7 @@ export type InstalledEngine =
|
|
|
197
207
|
| InstalledClickHouseEngine
|
|
198
208
|
| InstalledQdrantEngine
|
|
199
209
|
| InstalledMeilisearchEngine
|
|
210
|
+
| InstalledCouchDBEngine
|
|
200
211
|
|
|
201
212
|
async function getPostgresVersion(binPath: string): Promise<string | null> {
|
|
202
213
|
const ext = platformService.getExecutableExtension()
|
|
@@ -813,6 +824,58 @@ async function getInstalledMeilisearchEngines(): Promise<InstalledMeilisearchEng
|
|
|
813
824
|
return engines
|
|
814
825
|
}
|
|
815
826
|
|
|
827
|
+
// Get CouchDB version from binary path
|
|
828
|
+
// Note: CouchDB doesn't support --version flag, so we just verify the binary exists
|
|
829
|
+
// and return null to use the version from the directory name
|
|
830
|
+
async function getCouchDBVersion(binPath: string): Promise<string | null> {
|
|
831
|
+
const ext = platformService.getExecutableExtension()
|
|
832
|
+
const couchdbPath = join(binPath, 'bin', `couchdb${ext}`)
|
|
833
|
+
if (!existsSync(couchdbPath)) {
|
|
834
|
+
return null
|
|
835
|
+
}
|
|
836
|
+
// CouchDB is an Erlang app that tries to start when run with any args
|
|
837
|
+
// Just return null to use directory-parsed version
|
|
838
|
+
return null
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Get installed CouchDB engines from downloaded binaries
|
|
842
|
+
async function getInstalledCouchDBEngines(): Promise<InstalledCouchDBEngine[]> {
|
|
843
|
+
const binDir = paths.bin
|
|
844
|
+
|
|
845
|
+
if (!existsSync(binDir)) {
|
|
846
|
+
return []
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const entries = await readdir(binDir, { withFileTypes: true })
|
|
850
|
+
const engines: InstalledCouchDBEngine[] = []
|
|
851
|
+
|
|
852
|
+
for (const entry of entries) {
|
|
853
|
+
if (!entry.isDirectory()) continue
|
|
854
|
+
if (!entry.name.startsWith('couchdb-')) continue
|
|
855
|
+
|
|
856
|
+
const parsed = parseEngineDirectory(entry.name, 'couchdb-', binDir)
|
|
857
|
+
if (!parsed) continue
|
|
858
|
+
|
|
859
|
+
const actualVersion =
|
|
860
|
+
(await getCouchDBVersion(parsed.path)) || parsed.version
|
|
861
|
+
const sizeBytes = await calculateDirectorySize(parsed.path)
|
|
862
|
+
|
|
863
|
+
engines.push({
|
|
864
|
+
engine: 'couchdb',
|
|
865
|
+
version: actualVersion,
|
|
866
|
+
platform: parsed.platform,
|
|
867
|
+
arch: parsed.arch,
|
|
868
|
+
path: parsed.path,
|
|
869
|
+
sizeBytes,
|
|
870
|
+
source: 'downloaded',
|
|
871
|
+
})
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
engines.sort((a, b) => compareVersions(b.version, a.version))
|
|
875
|
+
|
|
876
|
+
return engines
|
|
877
|
+
}
|
|
878
|
+
|
|
816
879
|
// Get FerretDB version from binary path
|
|
817
880
|
async function getFerretDBVersion(binPath: string): Promise<string | null> {
|
|
818
881
|
const ext = platformService.getExecutableExtension()
|
|
@@ -899,6 +962,7 @@ const ENGINE_PREFIXES = [
|
|
|
899
962
|
'clickhouse-',
|
|
900
963
|
'qdrant-',
|
|
901
964
|
'meilisearch-',
|
|
965
|
+
'couchdb-',
|
|
902
966
|
] as const
|
|
903
967
|
|
|
904
968
|
/**
|
|
@@ -941,6 +1005,7 @@ export async function getInstalledEngines(): Promise<InstalledEngine[]> {
|
|
|
941
1005
|
clickhouseEngines,
|
|
942
1006
|
qdrantEngines,
|
|
943
1007
|
meilisearchEngines,
|
|
1008
|
+
couchdbEngines,
|
|
944
1009
|
] = await Promise.all([
|
|
945
1010
|
getInstalledPostgresEngines(),
|
|
946
1011
|
getInstalledMariadbEngines(),
|
|
@@ -954,6 +1019,7 @@ export async function getInstalledEngines(): Promise<InstalledEngine[]> {
|
|
|
954
1019
|
getInstalledClickHouseEngines(),
|
|
955
1020
|
getInstalledQdrantEngines(),
|
|
956
1021
|
getInstalledMeilisearchEngines(),
|
|
1022
|
+
getInstalledCouchDBEngines(),
|
|
957
1023
|
])
|
|
958
1024
|
|
|
959
1025
|
return [
|
|
@@ -969,6 +1035,7 @@ export async function getInstalledEngines(): Promise<InstalledEngine[]> {
|
|
|
969
1035
|
...clickhouseEngines,
|
|
970
1036
|
...qdrantEngines,
|
|
971
1037
|
...meilisearchEngines,
|
|
1038
|
+
...couchdbEngines,
|
|
972
1039
|
]
|
|
973
1040
|
}
|
|
974
1041
|
|
|
@@ -984,4 +1051,5 @@ export {
|
|
|
984
1051
|
getInstalledClickHouseEngines,
|
|
985
1052
|
getInstalledQdrantEngines,
|
|
986
1053
|
getInstalledMeilisearchEngines,
|
|
1054
|
+
getInstalledCouchDBEngines,
|
|
987
1055
|
}
|
package/cli/ui/prompts.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
installEngineDependencies,
|
|
16
16
|
} from '../../core/dependency-manager'
|
|
17
17
|
import { getEngineDependencies } from '../../config/os-dependencies'
|
|
18
|
-
import { getEngineIcon } from '../constants'
|
|
18
|
+
import { getEngineIcon, getEngineIconPadded } from '../constants'
|
|
19
19
|
import { type ContainerConfig, type Engine, type BackupFormatType } from '../../types'
|
|
20
20
|
|
|
21
21
|
// Navigation sentinel values for menu navigation
|
|
@@ -221,7 +221,7 @@ export async function promptEngine(options?: {
|
|
|
221
221
|
| inquirer.Separator
|
|
222
222
|
|
|
223
223
|
const choices: Choice[] = engines.map((e) => ({
|
|
224
|
-
name: `${
|
|
224
|
+
name: `${getEngineIconPadded(e.name)} ${e.displayName} ${chalk.gray(`(versions: ${e.supportedVersions.join(', ')})`)}`,
|
|
225
225
|
value: e.name,
|
|
226
226
|
short: e.displayName,
|
|
227
227
|
}))
|
|
@@ -233,6 +233,7 @@ export async function promptEngine(options?: {
|
|
|
233
233
|
name: `${chalk.blue('⌂')} Back to main menu ${chalk.gray('(esc)')}`,
|
|
234
234
|
value: MAIN_MENU_VALUE,
|
|
235
235
|
})
|
|
236
|
+
choices.push(new inquirer.Separator())
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
const { engine } = await escapeablePrompt<{ engine: string }>([
|
package/config/backup-formats.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type ClickHouseFormat,
|
|
23
23
|
type QdrantFormat,
|
|
24
24
|
type MeilisearchFormat,
|
|
25
|
+
type CouchDBFormat,
|
|
25
26
|
type BackupFormatType,
|
|
26
27
|
} from '../types'
|
|
27
28
|
|
|
@@ -53,6 +54,7 @@ export const BACKUP_FORMATS: {
|
|
|
53
54
|
[Engine.ClickHouse]: EngineBackupFormats<ClickHouseFormat>
|
|
54
55
|
[Engine.Qdrant]: EngineBackupFormats<QdrantFormat>
|
|
55
56
|
[Engine.Meilisearch]: EngineBackupFormats<MeilisearchFormat>
|
|
57
|
+
[Engine.CouchDB]: EngineBackupFormats<CouchDBFormat>
|
|
56
58
|
} = {
|
|
57
59
|
[Engine.PostgreSQL]: {
|
|
58
60
|
formats: {
|
|
@@ -252,6 +254,18 @@ export const BACKUP_FORMATS: {
|
|
|
252
254
|
supportsFormatChoice: false, // Only snapshot format supported
|
|
253
255
|
defaultFormat: 'snapshot',
|
|
254
256
|
},
|
|
257
|
+
[Engine.CouchDB]: {
|
|
258
|
+
formats: {
|
|
259
|
+
json: {
|
|
260
|
+
extension: '.json',
|
|
261
|
+
label: '.json',
|
|
262
|
+
description: 'JSON backup - all documents exported as JSON',
|
|
263
|
+
spinnerLabel: 'JSON',
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
supportsFormatChoice: false, // Only JSON format supported
|
|
267
|
+
defaultFormat: 'json',
|
|
268
|
+
},
|
|
255
269
|
}
|
|
256
270
|
|
|
257
271
|
/**
|