spindb 0.12.2 → 0.13.2

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 CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **The first npm CLI for running local databases without Docker.**
9
9
 
10
- Spin up PostgreSQL, MySQL, SQLite, and MongoDB instances for local development. No Docker daemon, no container networking, no volume mounts. Just databases running on localhost, ready in seconds.
10
+ Spin up PostgreSQL, MySQL, SQLite, MongoDB, and Redis instances for local development. No Docker daemon, no container networking, no volume mounts. Just databases running on localhost, ready in seconds.
11
11
 
12
12
  ---
13
13
 
@@ -145,7 +145,7 @@ SpinDB downloads PostgreSQL server binaries automatically:
145
145
  - **macOS/Linux:** Pre-compiled binaries from the zonky.io project, hosted on Maven Central
146
146
  - **Windows:** Official binaries from EnterpriseDB (EDB)
147
147
 
148
- **Why download binaries instead of using system PostgreSQL?** You might want PostgreSQL 14 for one project and 18 for another. SpinDB lets you run different versions side-by-side without conflicts.
148
+ **Why download binaries instead of using system PostgreSQL?** The [zonky.io project](https://github.com/zonkyio/embedded-postgres-binaries) provides pre-configured, portable PostgreSQL binaries—just extract and run. This lets you run PostgreSQL 14 for one project and 18 for another, side-by-side, without conflicts. No other database engine has an equivalent portable distribution.
149
149
 
150
150
  **Client tools required:** You still need `psql`, `pg_dump`, and `pg_restore` installed on your system for some operations (connecting, backups, restores). SpinDB can install these for you:
151
151
 
@@ -162,7 +162,7 @@ spindb deps install --engine postgresql
162
162
  | Default user | `root` |
163
163
  | Binary source | System installation |
164
164
 
165
- Unlike PostgreSQL, SpinDB uses your system's MySQL installation. This is because MySQL doesn't have a nice cross-platform binary distribution like zonky.io provides for PostgreSQL.
165
+ Unlike PostgreSQL, SpinDB uses your system's MySQL installation. While Oracle provides MySQL binary downloads, they require system libraries and configuration—there's no "unzip and run" distribution like zonky.io provides for PostgreSQL. For most local development, a single system-installed MySQL version works well.
166
166
 
167
167
  ```bash
168
168
  # macOS
@@ -219,7 +219,7 @@ spindb connect mydb --litecli
219
219
  | Default user | None (no auth by default) |
220
220
  | Binary source | System installation |
221
221
 
222
- Like MySQL, SpinDB uses your system's MongoDB installation. MongoDB binaries aren't available as a single cross-platform download, so you'll need to install MongoDB via your package manager.
222
+ Like MySQL, SpinDB uses your system's MongoDB installation. While MongoDB provides official binary downloads, they require additional configuration and system dependencies. SpinDB relies on your package manager to handle this setup.
223
223
 
224
224
  ```bash
225
225
  # macOS
@@ -240,20 +240,57 @@ MongoDB uses JavaScript for queries instead of SQL. When using `spindb run`, pas
240
240
 
241
241
  ```bash
242
242
  # Insert a document
243
- spindb run mydb --sql "db.users.insertOne({name: 'Alice', email: 'alice@example.com'})"
243
+ spindb run mydb -c "db.users.insertOne({name: 'Alice', email: 'alice@example.com'})"
244
244
 
245
245
  # Query documents
246
- spindb run mydb --sql "db.users.find().pretty()"
246
+ spindb run mydb -c "db.users.find().pretty()"
247
247
 
248
248
  # Run a JavaScript file
249
249
  spindb run mydb --file ./scripts/seed.js
250
250
  ```
251
251
 
252
- ### Planned Engines
252
+ #### Redis
253
253
 
254
- | Engine | Type | Status |
255
- |--------|------|--------|
256
- | Redis | In-memory key-value | Planned for v1.2 |
254
+ | | |
255
+ |---|---|
256
+ | Versions | 6, 7, 8 |
257
+ | Default port | 6379 |
258
+ | Default user | None (no auth by default) |
259
+ | Binary source | System installation |
260
+
261
+ Like MySQL and MongoDB, SpinDB uses your system's Redis installation. Redis provides embeddable binaries, but system packages are more reliable for handling dependencies and platform-specific setup.
262
+
263
+ ```bash
264
+ # macOS
265
+ brew install redis
266
+
267
+ # Ubuntu/Debian
268
+ sudo apt install redis-server redis-tools
269
+
270
+ # Windows (Chocolatey)
271
+ choco install redis
272
+
273
+ # Check if SpinDB can find Redis
274
+ spindb deps check --engine redis
275
+ ```
276
+
277
+ Redis uses numbered databases (0-15) instead of named databases. When using `spindb run`, pass Redis commands:
278
+
279
+ ```bash
280
+ # Set a key
281
+ spindb run myredis -c "SET mykey myvalue"
282
+
283
+ # Get a key
284
+ spindb run myredis -c "GET mykey"
285
+
286
+ # Run a Redis command file
287
+ spindb run myredis --file ./scripts/seed.redis
288
+
289
+ # Use iredis for enhanced shell experience
290
+ spindb connect myredis --iredis
291
+ ```
292
+
293
+ **Note:** Redis doesn't support remote dump/restore. Creating containers from remote Redis connection strings is not supported. Use `backup` and `restore` commands for data migration.
257
294
 
258
295
  ---
259
296
 
@@ -267,7 +304,7 @@ spindb run mydb --file ./scripts/seed.js
267
304
  spindb create mydb # PostgreSQL (default)
268
305
  spindb create mydb --engine mysql # MySQL
269
306
  spindb create mydb --engine sqlite # SQLite (file-based)
270
- spindb create mydb --version 16 # Specific PostgreSQL version
307
+ spindb create mydb --db-version 16 # Specific PostgreSQL version
271
308
  spindb create mydb --port 5433 # Custom port
272
309
  spindb create mydb --database my_app # Custom database name
273
310
  spindb create mydb --no-start # Create without starting
@@ -291,10 +328,10 @@ spindb create mydb --from "postgresql://user:pass@host:5432/production"
291
328
 
292
329
  | Option | Description |
293
330
  |--------|-------------|
294
- | `--engine`, `-e` | Database engine (`postgresql`, `mysql`, `sqlite`) |
295
- | `--version`, `-v` | Engine version |
331
+ | `--engine`, `-e` | Database engine (`postgresql`, `mysql`, `sqlite`, `mongodb`, `redis`) |
332
+ | `--db-version` | Engine version (e.g., 17 for PostgreSQL, 8 for Redis) |
296
333
  | `--port`, `-p` | Port number (not applicable for SQLite) |
297
- | `--database`, `-d` | Primary database name |
334
+ | `--database`, `-d` | Primary database name (Redis uses 0-15) |
298
335
  | `--path` | File path for SQLite databases |
299
336
  | `--max-connections` | Maximum database connections (default: 200) |
300
337
  | `--from` | Restore from backup file or connection string |
@@ -355,16 +392,20 @@ spindb connect mydb --install-mycli
355
392
  spindb connect mydb --install-tui
356
393
  ```
357
394
 
358
- #### `run` - Execute SQL/scripts
395
+ #### `run` - Execute SQL/scripts/commands
359
396
 
360
397
  ```bash
361
- spindb run mydb script.sql # Run a SQL file
362
- spindb run mydb --sql "SELECT * FROM users" # Run inline SQL
363
- spindb run mydb seed.sql --database my_app # Target specific database
398
+ spindb run mydb script.sql # Run a SQL file
399
+ spindb run mydb -c "SELECT * FROM users" # Run inline SQL
400
+ spindb run mydb seed.sql --database my_app # Target specific database
364
401
 
365
402
  # MongoDB uses JavaScript instead of SQL
366
- spindb run mydb seed.js # Run a JavaScript file
367
- spindb run mydb --sql "db.users.find().pretty()" # Run inline JavaScript
403
+ spindb run mydb seed.js # Run a JavaScript file
404
+ spindb run mydb -c "db.users.find().pretty()" # Run inline JavaScript
405
+
406
+ # Redis uses Redis commands
407
+ spindb run myredis -c "SET foo bar" # Run inline command
408
+ spindb run myredis seed.redis # Run command file
368
409
  ```
369
410
 
370
411
  #### `url` - Get connection string
@@ -596,7 +637,7 @@ SpinDB supports enhanced database shells that provide features like auto-complet
596
637
  | MySQL | `mysql` | `mycli` | `usql` |
597
638
  | SQLite | `sqlite3` | `litecli` | `usql` |
598
639
  | MongoDB | `mongosh` | - | `usql` |
599
- | Redis (planned) | `redis-cli` | `iredis` | - |
640
+ | Redis | `redis-cli` | `iredis` | - |
600
641
 
601
642
  **pgcli / mycli** provide:
602
643
  - Intelligent auto-completion (tables, columns, keywords)
@@ -682,15 +723,38 @@ When you stop a container:
682
723
  - **macOS/Linux:** From [zonky.io/embedded-postgres-binaries](https://github.com/zonkyio/embedded-postgres-binaries), hosted on Maven Central
683
724
  - **Windows:** From [EnterpriseDB (EDB)](https://www.enterprisedb.com/download-postgresql-binaries), official PostgreSQL distributions
684
725
 
685
- **MySQL:** Uses your system's MySQL installation. SpinDB detects binaries from Homebrew (macOS), apt/pacman (Linux), or Chocolatey/winget/Scoop (Windows).
726
+ **MySQL/MongoDB/Redis:** Uses your system installation. SpinDB detects binaries from Homebrew (macOS), apt/pacman (Linux), or Chocolatey/winget/Scoop (Windows).
727
+
728
+ ### Why Precompiled Binaries for PostgreSQL, but System Installs for Others?
729
+
730
+ This isn't a preference—it's a practical reality of what's available.
731
+
732
+ **PostgreSQL has an excellent embedded binary distribution.** The [zonky.io](https://github.com/zonkyio/embedded-postgres-binaries) project maintains minimal, self-contained PostgreSQL server binaries specifically designed for embedding:
733
+
734
+ - Cross-platform (macOS Intel/ARM, Linux x64/ARM, Windows)
735
+ - Hosted on Maven Central (highly reliable CDN)
736
+ - ~45 MB per version
737
+ - Actively maintained with new PostgreSQL releases
738
+
739
+ This makes multi-version support trivial: need PostgreSQL 14 for a legacy project and 18 for a new one? SpinDB downloads both, and they run side-by-side without conflicts.
740
+
741
+ **No equivalent exists for MySQL or MongoDB.** Neither database has a comparable embedded binary project:
742
+
743
+ - **MySQL:** Oracle distributes MySQL as large installers with system dependencies, not embeddable binaries. There's no "zonky.io for MySQL."
744
+ - **MongoDB:** Server binaries are several hundred MB with complex licensing around redistribution. MongoDB Inc. doesn't provide an embedded distribution.
745
+
746
+ For these databases, system packages (Homebrew, apt, choco) are the most reliable option. They handle dependencies, platform quirks, and security updates. SpinDB simply orchestrates what's already installed.
747
+
748
+ **Does this limit multi-version support?** Yes, for MySQL/MongoDB you get whatever version your package manager provides. In practice, this is rarely a problem—developers seldom need multiple MySQL versions simultaneously. If zonky.io-style distributions emerged for other databases, SpinDB could adopt them.
686
749
 
687
750
  ---
688
751
 
689
752
  ## Limitations
690
753
 
691
- - **Client tools required** - `psql` and `mysql` must be installed separately for some operations
754
+ - **Client tools required** - `psql`, `mysql`, `mongosh`, and `redis-cli` must be installed separately for some operations (connecting, backups, restores)
692
755
  - **Local only** - Databases bind to `127.0.0.1`; remote connections planned for v1.1
693
- - **MySQL requires system install** - Unlike PostgreSQL, we don't download MySQL binaries
756
+ - **Single version for MySQL/MongoDB/Redis** - Unlike PostgreSQL, MySQL, MongoDB, and Redis use system installations, so you're limited to one version per machine (see [Why Precompiled Binaries for PostgreSQL?](#why-precompiled-binaries-for-postgresql-but-system-installs-for-others))
757
+ - **Redis remote dump not supported** - Redis doesn't support creating containers from remote connection strings. Use backup/restore for data migration.
694
758
 
695
759
  ---
696
760
 
@@ -704,9 +768,8 @@ See [TODO.md](TODO.md) for the full roadmap.
704
768
  - Secrets management (macOS Keychain)
705
769
 
706
770
  ### v1.2 - Additional Engines
707
- - Redis (in-memory key-value)
708
- - MongoDB (document database)
709
771
  - MariaDB as standalone engine
772
+ - CockroachDB (distributed SQL)
710
773
 
711
774
  ### v1.3 - Advanced Features
712
775
  - Container templates
@@ -9,15 +9,18 @@ import {
9
9
  isPgcliInstalled,
10
10
  isMycliInstalled,
11
11
  isLitecliInstalled,
12
+ isIredisInstalled,
12
13
  detectPackageManager,
13
14
  installUsql,
14
15
  installPgcli,
15
16
  installMycli,
16
17
  installLitecli,
18
+ installIredis,
17
19
  getUsqlManualInstructions,
18
20
  getPgcliManualInstructions,
19
21
  getMycliManualInstructions,
20
22
  getLitecliManualInstructions,
23
+ getIredisManualInstructions,
21
24
  } from '../../core/dependency-manager'
22
25
  import { getEngine } from '../../engines'
23
26
  import { getEngineDefaults } from '../../config/defaults'
@@ -47,6 +50,11 @@ export const connectCommand = new Command('connect')
47
50
  'Use litecli for enhanced SQLite shell (auto-completion, syntax highlighting)',
48
51
  )
49
52
  .option('--install-litecli', 'Install litecli if not present, then connect')
53
+ .option(
54
+ '--iredis',
55
+ 'Use iredis for enhanced Redis shell (auto-completion, syntax highlighting)',
56
+ )
57
+ .option('--install-iredis', 'Install iredis if not present, then connect')
50
58
  .action(
51
59
  async (
52
60
  name: string | undefined,
@@ -60,6 +68,8 @@ export const connectCommand = new Command('connect')
60
68
  installMycli?: boolean
61
69
  litecli?: boolean
62
70
  installLitecli?: boolean
71
+ iredis?: boolean
72
+ installIredis?: boolean
63
73
  },
64
74
  ) => {
65
75
  try {
@@ -376,6 +386,76 @@ export const connectCommand = new Command('connect')
376
386
  }
377
387
  }
378
388
 
389
+ const useIredis = options.iredis || options.installIredis
390
+ if (useIredis) {
391
+ if (engineName !== Engine.Redis) {
392
+ console.error(
393
+ uiError('iredis is only available for Redis containers'),
394
+ )
395
+ if (engineName === 'postgresql') {
396
+ console.log(
397
+ chalk.gray('For PostgreSQL, use: spindb connect --pgcli'),
398
+ )
399
+ } else if (engineName === 'mysql') {
400
+ console.log(chalk.gray('For MySQL, use: spindb connect --mycli'))
401
+ } else if (engineName === Engine.SQLite) {
402
+ console.log(
403
+ chalk.gray('For SQLite, use: spindb connect --litecli'),
404
+ )
405
+ }
406
+ process.exit(1)
407
+ }
408
+
409
+ const iredisInstalled = await isIredisInstalled()
410
+
411
+ if (!iredisInstalled) {
412
+ if (options.installIredis) {
413
+ console.log(
414
+ uiInfo('Installing iredis for enhanced Redis shell...'),
415
+ )
416
+ const pm = await detectPackageManager()
417
+ if (pm) {
418
+ const result = await installIredis(pm)
419
+ if (result.success) {
420
+ console.log(uiSuccess('iredis installed successfully!'))
421
+ console.log()
422
+ } else {
423
+ console.error(
424
+ uiError(`Failed to install iredis: ${result.error}`),
425
+ )
426
+ console.log()
427
+ console.log(chalk.gray('Manual installation:'))
428
+ for (const instruction of getIredisManualInstructions()) {
429
+ console.log(chalk.cyan(` ${instruction}`))
430
+ }
431
+ process.exit(1)
432
+ }
433
+ } else {
434
+ console.error(uiError('No supported package manager found'))
435
+ console.log()
436
+ console.log(chalk.gray('Manual installation:'))
437
+ for (const instruction of getIredisManualInstructions()) {
438
+ console.log(chalk.cyan(` ${instruction}`))
439
+ }
440
+ process.exit(1)
441
+ }
442
+ } else {
443
+ console.error(uiError('iredis is not installed'))
444
+ console.log()
445
+ console.log(
446
+ chalk.gray('Install iredis for enhanced Redis shell:'),
447
+ )
448
+ console.log(chalk.cyan(' spindb connect --install-iredis'))
449
+ console.log()
450
+ console.log(chalk.gray('Or install manually:'))
451
+ for (const instruction of getIredisManualInstructions()) {
452
+ console.log(chalk.cyan(` ${instruction}`))
453
+ }
454
+ process.exit(1)
455
+ }
456
+ }
457
+ }
458
+
379
459
  console.log(uiInfo(`Connecting to ${containerName}:${database}...`))
380
460
  console.log()
381
461
 
@@ -385,6 +465,9 @@ export const connectCommand = new Command('connect')
385
465
  if (useLitecli) {
386
466
  clientCmd = 'litecli'
387
467
  clientArgs = [config.database]
468
+ } else if (useIredis) {
469
+ clientCmd = 'iredis'
470
+ clientArgs = ['-h', '127.0.0.1', '-p', String(config.port), '-n', database]
388
471
  } else if (usePgcli) {
389
472
  clientCmd = 'pgcli'
390
473
  clientArgs = [connectionString]
@@ -405,6 +488,9 @@ export const connectCommand = new Command('connect')
405
488
  } else if (engineName === Engine.SQLite) {
406
489
  clientCmd = 'sqlite3'
407
490
  clientArgs = [config.database]
491
+ } else if (engineName === Engine.Redis) {
492
+ clientCmd = 'redis-cli'
493
+ clientArgs = ['-h', '127.0.0.1', '-p', String(config.port), '-n', database]
408
494
  } else if (engineName === 'mysql') {
409
495
  clientCmd = 'mysql'
410
496
  clientArgs = [
@@ -449,6 +535,12 @@ export const connectCommand = new Command('connect')
449
535
  } else if (clientCmd === 'litecli') {
450
536
  console.log(chalk.gray(' Install litecli:'))
451
537
  console.log(chalk.cyan(' brew install litecli'))
538
+ } else if (clientCmd === 'iredis') {
539
+ console.log(chalk.gray(' Install iredis:'))
540
+ console.log(chalk.cyan(' pip install iredis'))
541
+ } else if (clientCmd === 'redis-cli') {
542
+ console.log(chalk.gray(' Install Redis:'))
543
+ console.log(chalk.cyan(' brew install redis'))
452
544
  } else if (clientCmd === 'sqlite3') {
453
545
  console.log(chalk.gray(' sqlite3 comes with macOS.'))
454
546
  console.log(chalk.gray(' If not available, check your PATH.'))
@@ -178,6 +178,10 @@ function detectLocationType(location: string): {
178
178
  return { type: 'connection', inferredEngine: Engine.SQLite }
179
179
  }
180
180
 
181
+ if (location.startsWith('redis://') || location.startsWith('rediss://')) {
182
+ return { type: 'connection', inferredEngine: Engine.Redis }
183
+ }
184
+
181
185
  if (existsSync(location)) {
182
186
  // Check if it's a SQLite file (case-insensitive)
183
187
  const lowerLocation = location.toLowerCase()
@@ -199,9 +203,9 @@ export const createCommand = new Command('create')
199
203
  .argument('[name]', 'Container name')
200
204
  .option(
201
205
  '-e, --engine <engine>',
202
- 'Database engine (postgresql, mysql, sqlite)',
206
+ 'Database engine (postgresql, mysql, sqlite, mongodb, redis)',
203
207
  )
204
- .option('-v, --version <version>', 'Database version')
208
+ .option('--db-version <version>', 'Database version (e.g., 17, 8.0)')
205
209
  .option('-d, --database <database>', 'Database name')
206
210
  .option('-p, --port <port>', 'Port number')
207
211
  .option(
@@ -225,7 +229,7 @@ export const createCommand = new Command('create')
225
229
  name: string | undefined,
226
230
  options: {
227
231
  engine?: string
228
- version?: string
232
+ dbVersion?: string
229
233
  database?: string
230
234
  port?: string
231
235
  path?: string
@@ -241,7 +245,7 @@ export const createCommand = new Command('create')
241
245
  try {
242
246
  let containerName = name
243
247
  let engine: Engine = (options.engine as Engine) || Engine.PostgreSQL
244
- let version = options.version
248
+ let version = options.dbVersion
245
249
  let database = options.database
246
250
 
247
251
  let restoreLocation: string | null = null
@@ -296,10 +300,17 @@ export const createCommand = new Command('create')
296
300
  database = answers.database
297
301
  }
298
302
 
299
- database = database ?? containerName
303
+ // Redis uses numbered databases (0-15), default to "0"
304
+ // Other engines default to container name
305
+ if (engine === Engine.Redis) {
306
+ database = database ?? '0'
307
+ } else {
308
+ database = database ?? containerName
309
+ }
300
310
 
301
311
  // Validate database name to prevent SQL injection
302
- if (!isValidDatabaseName(database)) {
312
+ // Skip for Redis which uses numbered databases (0-15)
313
+ if (engine !== Engine.Redis && !isValidDatabaseName(database)) {
303
314
  console.error(
304
315
  uiError(
305
316
  'Database name must start with a letter and contain only letters, numbers, hyphens, and underscores',
@@ -24,6 +24,8 @@ import {
24
24
  type InstalledPostgresEngine,
25
25
  type InstalledMysqlEngine,
26
26
  type InstalledSqliteEngine,
27
+ type InstalledMongodbEngine,
28
+ type InstalledRedisEngine,
27
29
  } from '../helpers'
28
30
  import { Engine } from '../../types'
29
31
 
@@ -36,6 +38,25 @@ function padWithEmoji(str: string, width: number): string {
36
38
  return str.padEnd(width + emojiCount)
37
39
  }
38
40
 
41
+ /**
42
+ * Display manual installation instructions for missing dependencies
43
+ */
44
+ function displayManualInstallInstructions(
45
+ missingDeps: Array<{ dependency: { name: string }; installed: boolean }>,
46
+ ): void {
47
+ const platform = getCurrentPlatform()
48
+ for (const status of missingDeps) {
49
+ const instructions = getManualInstallInstructions(
50
+ status.dependency as Parameters<typeof getManualInstallInstructions>[0],
51
+ platform,
52
+ )
53
+ console.log(chalk.gray(` ${status.dependency.name}:`))
54
+ for (const instruction of instructions) {
55
+ console.log(chalk.gray(` ${instruction}`))
56
+ }
57
+ }
58
+ }
59
+
39
60
  /**
40
61
  * List subcommand action
41
62
  */
@@ -72,6 +93,12 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
72
93
  const sqliteEngine = engines.find(
73
94
  (e): e is InstalledSqliteEngine => e.engine === 'sqlite',
74
95
  )
96
+ const mongodbEngine = engines.find(
97
+ (e): e is InstalledMongodbEngine => e.engine === 'mongodb',
98
+ )
99
+ const redisEngine = engines.find(
100
+ (e): e is InstalledRedisEngine => e.engine === 'redis',
101
+ )
75
102
 
76
103
  // Calculate total size for PostgreSQL
77
104
  const totalPgSize = pgEngines.reduce((acc, e) => acc + e.sizeBytes, 0)
@@ -131,6 +158,34 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
131
158
  )
132
159
  }
133
160
 
161
+ // MongoDB row
162
+ if (mongodbEngine) {
163
+ const icon = ENGINE_ICONS.mongodb
164
+ const engineDisplay = `${icon} mongodb`
165
+
166
+ console.log(
167
+ chalk.gray(' ') +
168
+ chalk.cyan(padWithEmoji(engineDisplay, 13)) +
169
+ chalk.yellow(mongodbEngine.version.padEnd(12)) +
170
+ chalk.gray('system'.padEnd(18)) +
171
+ chalk.gray('(system-installed)'),
172
+ )
173
+ }
174
+
175
+ // Redis row
176
+ if (redisEngine) {
177
+ const icon = ENGINE_ICONS.redis
178
+ const engineDisplay = `${icon} redis`
179
+
180
+ console.log(
181
+ chalk.gray(' ') +
182
+ chalk.cyan(padWithEmoji(engineDisplay, 13)) +
183
+ chalk.yellow(redisEngine.version.padEnd(12)) +
184
+ chalk.gray('system'.padEnd(18)) +
185
+ chalk.gray('(system-installed)'),
186
+ )
187
+ }
188
+
134
189
  console.log(chalk.gray(' ' + '─'.repeat(55)))
135
190
 
136
191
  // Summary
@@ -150,6 +205,16 @@ async function listEngines(options: { json?: boolean }): Promise<void> {
150
205
  chalk.gray(` SQLite: system-installed at ${sqliteEngine.path}`),
151
206
  )
152
207
  }
208
+ if (mongodbEngine) {
209
+ console.log(
210
+ chalk.gray(` MongoDB: system-installed at ${mongodbEngine.path}`),
211
+ )
212
+ }
213
+ if (redisEngine) {
214
+ console.log(
215
+ chalk.gray(` Redis: system-installed at ${redisEngine.path}`),
216
+ )
217
+ }
153
218
  console.log()
154
219
  }
155
220
 
@@ -283,18 +348,8 @@ async function installEngineViaPackageManager(
283
348
  console.error(uiError('No supported package manager found.'))
284
349
  console.log()
285
350
  console.log(chalk.yellow('Manual installation instructions:'))
286
- const platform = getCurrentPlatform()
287
351
  const missingDeps = statuses.filter((s) => !s.installed)
288
- for (const status of missingDeps) {
289
- const instructions = getManualInstallInstructions(
290
- status.dependency,
291
- platform,
292
- )
293
- console.log(chalk.gray(` ${status.dependency.name}:`))
294
- for (const instruction of instructions) {
295
- console.log(chalk.gray(` ${instruction}`))
296
- }
297
- }
352
+ displayManualInstallInstructions(missingDeps)
298
353
  process.exit(1)
299
354
  }
300
355
 
@@ -329,6 +384,24 @@ async function installEngineViaPackageManager(
329
384
  }
330
385
  process.exit(1)
331
386
  }
387
+
388
+ // Check if some dependencies couldn't be installed because the package manager
389
+ // doesn't have a package definition for them (e.g., Redis on Windows with Chocolatey)
390
+ if (results.length === 0) {
391
+ const stillMissing = statuses.filter((s) => !s.installed)
392
+ if (stillMissing.length > 0) {
393
+ console.log()
394
+ console.log(
395
+ uiWarning(
396
+ `${packageManager.name} doesn't have packages for ${displayName}.`,
397
+ ),
398
+ )
399
+ console.log()
400
+ console.log(chalk.yellow('Manual installation required:'))
401
+ displayManualInstallInstructions(stillMissing)
402
+ process.exit(1)
403
+ }
404
+ }
332
405
  }
333
406
 
334
407
  // Main engines command
@@ -430,9 +503,19 @@ enginesCommand
430
503
  return
431
504
  }
432
505
 
506
+ if (['mongodb', 'mongo'].includes(normalizedEngine)) {
507
+ await installEngineViaPackageManager('mongodb', 'MongoDB')
508
+ return
509
+ }
510
+
511
+ if (normalizedEngine === 'redis') {
512
+ await installEngineViaPackageManager('redis', 'Redis')
513
+ return
514
+ }
515
+
433
516
  console.error(
434
517
  uiError(
435
- `Unknown engine "${engineName}". Supported: postgresql, mysql, sqlite`,
518
+ `Unknown engine "${engineName}". Supported: postgresql, mysql, sqlite, mongodb, redis`,
436
519
  ),
437
520
  )
438
521
  process.exit(1)
@@ -94,7 +94,13 @@ export async function handleCreate(): Promise<'main' | void> {
94
94
  const name = containerName!
95
95
 
96
96
  // Step 4: Database name (defaults to container name, sanitized)
97
- const database = await promptDatabaseName(name, engine)
97
+ // Redis uses numbered databases 0-15, so skip prompt and default to "0"
98
+ let database: string
99
+ if (engine === 'redis') {
100
+ database = '0'
101
+ } else {
102
+ database = await promptDatabaseName(name, engine)
103
+ }
98
104
 
99
105
  // Step 5: Port or SQLite path
100
106
  const isSQLite = engine === 'sqlite'
@@ -601,9 +607,13 @@ export async function showContainerSubmenu(
601
607
 
602
608
  // Run SQL/script - always enabled for SQLite (if file exists), server databases need to be running
603
609
  const canRunSql = isSQLite ? existsSync(config.database) : isRunning
604
- // MongoDB uses JavaScript scripts, not SQL
610
+ // Engine-specific terminology: Redis uses commands, MongoDB uses scripts, others use SQL
605
611
  const runScriptLabel =
606
- config.engine === 'mongodb' ? 'Run script file' : 'Run SQL file'
612
+ config.engine === 'redis'
613
+ ? 'Run command file'
614
+ : config.engine === 'mongodb'
615
+ ? 'Run script file'
616
+ : 'Run SQL file'
607
617
  actionChoices.push({
608
618
  name: canRunSql
609
619
  ? `${chalk.yellow('▷')} ${runScriptLabel}`