spindb 0.5.4 → 0.6.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/cli/ui/prompts.ts CHANGED
@@ -251,7 +251,8 @@ export async function promptDatabaseName(
251
251
  engine?: string,
252
252
  ): Promise<string> {
253
253
  // MySQL uses "schema" terminology (database and schema are synonymous)
254
- const label = engine === 'mysql' ? 'Database (schema) name:' : 'Database name:'
254
+ const label =
255
+ engine === 'mysql' ? 'Database (schema) name:' : 'Database name:'
255
256
 
256
257
  const { database } = await inquirer.prompt<{ database: string }>([
257
258
  {
@@ -276,6 +277,93 @@ export async function promptDatabaseName(
276
277
  return database
277
278
  }
278
279
 
280
+ /**
281
+ * Prompt to select a database from a list of databases in a container
282
+ */
283
+ export async function promptDatabaseSelect(
284
+ databases: string[],
285
+ message: string = 'Select database:',
286
+ ): Promise<string> {
287
+ if (databases.length === 0) {
288
+ throw new Error('No databases available to select')
289
+ }
290
+
291
+ if (databases.length === 1) {
292
+ return databases[0]
293
+ }
294
+
295
+ const { database } = await inquirer.prompt<{ database: string }>([
296
+ {
297
+ type: 'list',
298
+ name: 'database',
299
+ message,
300
+ choices: databases.map((db, index) => ({
301
+ name: index === 0 ? `${db} ${chalk.gray('(primary)')}` : db,
302
+ value: db,
303
+ short: db,
304
+ })),
305
+ },
306
+ ])
307
+
308
+ return database
309
+ }
310
+
311
+ /**
312
+ * Prompt for backup format selection
313
+ */
314
+ export async function promptBackupFormat(
315
+ engine: string,
316
+ ): Promise<'sql' | 'dump'> {
317
+ const sqlDescription =
318
+ engine === 'mysql'
319
+ ? 'Plain SQL - human-readable, larger file'
320
+ : 'Plain SQL - human-readable, larger file'
321
+ const dumpDescription =
322
+ engine === 'mysql'
323
+ ? 'Compressed SQL (.sql.gz) - smaller file'
324
+ : 'Custom format - smaller file, faster restore'
325
+
326
+ const { format } = await inquirer.prompt<{ format: 'sql' | 'dump' }>([
327
+ {
328
+ type: 'list',
329
+ name: 'format',
330
+ message: 'Select backup format:',
331
+ choices: [
332
+ { name: `.sql ${chalk.gray(`- ${sqlDescription}`)}`, value: 'sql' },
333
+ { name: `.dump ${chalk.gray(`- ${dumpDescription}`)}`, value: 'dump' },
334
+ ],
335
+ default: 'sql',
336
+ },
337
+ ])
338
+
339
+ return format
340
+ }
341
+
342
+ /**
343
+ * Prompt for backup filename
344
+ */
345
+ export async function promptBackupFilename(
346
+ defaultName: string,
347
+ ): Promise<string> {
348
+ const { filename } = await inquirer.prompt<{ filename: string }>([
349
+ {
350
+ type: 'input',
351
+ name: 'filename',
352
+ message: 'Backup filename (without extension):',
353
+ default: defaultName,
354
+ validate: (input: string) => {
355
+ if (!input) return 'Filename is required'
356
+ if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
357
+ return 'Filename must contain only letters, numbers, underscores, and hyphens'
358
+ }
359
+ return true
360
+ },
361
+ },
362
+ ])
363
+
364
+ return filename
365
+ }
366
+
279
367
  export type CreateOptions = {
280
368
  name: string
281
369
  engine: string
package/cli/ui/theme.ts CHANGED
@@ -155,3 +155,14 @@ export function connectionBox(
155
155
 
156
156
  return box(lines)
157
157
  }
158
+
159
+ /**
160
+ * Format bytes into human-readable format (B, KB, MB, GB)
161
+ */
162
+ export function formatBytes(bytes: number): string {
163
+ if (bytes === 0) return '0 B'
164
+ const units = ['B', 'KB', 'MB', 'GB', 'TB']
165
+ const i = Math.floor(Math.log(bytes) / Math.log(1024))
166
+ const value = bytes / Math.pow(1024, i)
167
+ return `${value.toFixed(1)} ${units[i]}`
168
+ }
@@ -18,6 +18,27 @@ const DEFAULT_CONFIG: SpinDBConfig = {
18
18
  binaries: {},
19
19
  }
20
20
 
21
+ // Cache staleness threshold (7 days in milliseconds)
22
+ const CACHE_STALENESS_MS = 7 * 24 * 60 * 60 * 1000
23
+
24
+ // All tools organized by category
25
+ const POSTGRESQL_TOOLS: BinaryTool[] = [
26
+ 'psql',
27
+ 'pg_dump',
28
+ 'pg_restore',
29
+ 'pg_basebackup',
30
+ ]
31
+
32
+ const MYSQL_TOOLS: BinaryTool[] = ['mysql', 'mysqldump', 'mysqladmin', 'mysqld']
33
+
34
+ const ENHANCED_SHELLS: BinaryTool[] = ['pgcli', 'mycli', 'usql']
35
+
36
+ const ALL_TOOLS: BinaryTool[] = [
37
+ ...POSTGRESQL_TOOLS,
38
+ ...MYSQL_TOOLS,
39
+ ...ENHANCED_SHELLS,
40
+ ]
41
+
21
42
  export class ConfigManager {
22
43
  private config: SpinDBConfig | null = null
23
44
 
@@ -170,44 +191,55 @@ export class ConfigManager {
170
191
  }
171
192
 
172
193
  /**
173
- * Get common installation paths for PostgreSQL client tools
194
+ * Get common installation paths for database tools
174
195
  */
175
196
  private getCommonBinaryPaths(tool: BinaryTool): string[] {
176
- const paths: string[] = []
177
-
178
- // Homebrew (macOS)
179
- paths.push(`/opt/homebrew/bin/${tool}`)
180
- paths.push(`/opt/homebrew/opt/libpq/bin/${tool}`)
181
- paths.push(`/usr/local/bin/${tool}`)
182
- paths.push(`/usr/local/opt/libpq/bin/${tool}`)
183
-
184
- // Postgres.app (macOS)
185
- paths.push(
186
- `/Applications/Postgres.app/Contents/Versions/latest/bin/${tool}`,
187
- )
188
-
189
- // Linux common paths
190
- paths.push(`/usr/bin/${tool}`)
191
- paths.push(`/usr/lib/postgresql/16/bin/${tool}`)
192
- paths.push(`/usr/lib/postgresql/15/bin/${tool}`)
193
- paths.push(`/usr/lib/postgresql/14/bin/${tool}`)
194
-
195
- return paths
197
+ const commonPaths: string[] = []
198
+
199
+ // Homebrew (macOS ARM)
200
+ commonPaths.push(`/opt/homebrew/bin/${tool}`)
201
+ // Homebrew (macOS Intel)
202
+ commonPaths.push(`/usr/local/bin/${tool}`)
203
+
204
+ // PostgreSQL-specific paths
205
+ if (POSTGRESQL_TOOLS.includes(tool) || tool === 'pgcli') {
206
+ commonPaths.push(`/opt/homebrew/opt/libpq/bin/${tool}`)
207
+ commonPaths.push(`/usr/local/opt/libpq/bin/${tool}`)
208
+ // Postgres.app (macOS)
209
+ commonPaths.push(
210
+ `/Applications/Postgres.app/Contents/Versions/latest/bin/${tool}`,
211
+ )
212
+ // Linux PostgreSQL paths
213
+ commonPaths.push(`/usr/lib/postgresql/17/bin/${tool}`)
214
+ commonPaths.push(`/usr/lib/postgresql/16/bin/${tool}`)
215
+ commonPaths.push(`/usr/lib/postgresql/15/bin/${tool}`)
216
+ commonPaths.push(`/usr/lib/postgresql/14/bin/${tool}`)
217
+ }
218
+
219
+ // MySQL-specific paths
220
+ if (MYSQL_TOOLS.includes(tool) || tool === 'mycli') {
221
+ commonPaths.push(`/opt/homebrew/opt/mysql/bin/${tool}`)
222
+ commonPaths.push(`/opt/homebrew/opt/mysql-client/bin/${tool}`)
223
+ commonPaths.push(`/usr/local/opt/mysql/bin/${tool}`)
224
+ commonPaths.push(`/usr/local/opt/mysql-client/bin/${tool}`)
225
+ // Linux MySQL/MariaDB paths
226
+ commonPaths.push(`/usr/bin/${tool}`)
227
+ commonPaths.push(`/usr/sbin/${tool}`)
228
+ }
229
+
230
+ // General Linux paths
231
+ commonPaths.push(`/usr/bin/${tool}`)
232
+
233
+ return commonPaths
196
234
  }
197
235
 
198
236
  /**
199
237
  * Detect all available client tools on the system
200
238
  */
201
239
  async detectAllTools(): Promise<Map<BinaryTool, string>> {
202
- const tools: BinaryTool[] = [
203
- 'psql',
204
- 'pg_dump',
205
- 'pg_restore',
206
- 'pg_basebackup',
207
- ]
208
240
  const found = new Map<BinaryTool, string>()
209
241
 
210
- for (const tool of tools) {
242
+ for (const tool of ALL_TOOLS) {
211
243
  const path = await this.detectSystemBinary(tool)
212
244
  if (path) {
213
245
  found.set(tool, path)
@@ -219,18 +251,19 @@ export class ConfigManager {
219
251
 
220
252
  /**
221
253
  * Initialize config by detecting all available tools
254
+ * Groups results by category for better display
222
255
  */
223
- async initialize(): Promise<{ found: BinaryTool[]; missing: BinaryTool[] }> {
224
- const tools: BinaryTool[] = [
225
- 'psql',
226
- 'pg_dump',
227
- 'pg_restore',
228
- 'pg_basebackup',
229
- ]
256
+ async initialize(): Promise<{
257
+ found: BinaryTool[]
258
+ missing: BinaryTool[]
259
+ postgresql: { found: BinaryTool[]; missing: BinaryTool[] }
260
+ mysql: { found: BinaryTool[]; missing: BinaryTool[] }
261
+ enhanced: { found: BinaryTool[]; missing: BinaryTool[] }
262
+ }> {
230
263
  const found: BinaryTool[] = []
231
264
  const missing: BinaryTool[] = []
232
265
 
233
- for (const tool of tools) {
266
+ for (const tool of ALL_TOOLS) {
234
267
  const path = await this.getBinaryPath(tool)
235
268
  if (path) {
236
269
  found.push(tool)
@@ -239,7 +272,57 @@ export class ConfigManager {
239
272
  }
240
273
  }
241
274
 
242
- return { found, missing }
275
+ return {
276
+ found,
277
+ missing,
278
+ postgresql: {
279
+ found: found.filter((t) => POSTGRESQL_TOOLS.includes(t)),
280
+ missing: missing.filter((t) => POSTGRESQL_TOOLS.includes(t)),
281
+ },
282
+ mysql: {
283
+ found: found.filter((t) => MYSQL_TOOLS.includes(t)),
284
+ missing: missing.filter((t) => MYSQL_TOOLS.includes(t)),
285
+ },
286
+ enhanced: {
287
+ found: found.filter((t) => ENHANCED_SHELLS.includes(t)),
288
+ missing: missing.filter((t) => ENHANCED_SHELLS.includes(t)),
289
+ },
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Check if the config cache is stale (older than 7 days)
295
+ */
296
+ async isStale(): Promise<boolean> {
297
+ const config = await this.load()
298
+ if (!config.updatedAt) {
299
+ return true
300
+ }
301
+
302
+ const updatedAt = new Date(config.updatedAt).getTime()
303
+ const now = Date.now()
304
+ return now - updatedAt > CACHE_STALENESS_MS
305
+ }
306
+
307
+ /**
308
+ * Refresh all tool paths if cache is stale
309
+ * Returns true if refresh was performed
310
+ */
311
+ async refreshIfStale(): Promise<boolean> {
312
+ if (await this.isStale()) {
313
+ await this.refreshAllBinaries()
314
+ return true
315
+ }
316
+ return false
317
+ }
318
+
319
+ /**
320
+ * Force refresh all binary paths
321
+ * Re-detects all tools and updates versions
322
+ */
323
+ async refreshAllBinaries(): Promise<void> {
324
+ await this.clearAllBinaries()
325
+ await this.initialize()
243
326
  }
244
327
 
245
328
  /**
@@ -269,3 +352,6 @@ export class ConfigManager {
269
352
  }
270
353
 
271
354
  export const configManager = new ConfigManager()
355
+
356
+ // Export tool categories for use in commands
357
+ export { POSTGRESQL_TOOLS, MYSQL_TOOLS, ENHANCED_SHELLS, ALL_TOOLS }
@@ -51,6 +51,7 @@ export class ContainerManager {
51
51
  version,
52
52
  port,
53
53
  database,
54
+ databases: [database],
54
55
  created: new Date().toISOString(),
55
56
  status: 'created',
56
57
  }
@@ -63,6 +64,7 @@ export class ContainerManager {
63
64
  /**
64
65
  * Get container configuration
65
66
  * If engine is not provided, searches all engine directories
67
+ * Automatically migrates old schemas to include databases array
66
68
  */
67
69
  async getConfig(
68
70
  name: string,
@@ -77,7 +79,8 @@ export class ContainerManager {
77
79
  return null
78
80
  }
79
81
  const content = await readFile(configPath, 'utf8')
80
- return JSON.parse(content) as ContainerConfig
82
+ const config = JSON.parse(content) as ContainerConfig
83
+ return this.migrateConfig(config)
81
84
  }
82
85
 
83
86
  // Search all engine directories
@@ -86,13 +89,43 @@ export class ContainerManager {
86
89
  const configPath = paths.getContainerConfigPath(name, { engine: eng })
87
90
  if (existsSync(configPath)) {
88
91
  const content = await readFile(configPath, 'utf8')
89
- return JSON.parse(content) as ContainerConfig
92
+ const config = JSON.parse(content) as ContainerConfig
93
+ return this.migrateConfig(config)
90
94
  }
91
95
  }
92
96
 
93
97
  return null
94
98
  }
95
99
 
100
+ /**
101
+ * Migrate old container configs to include databases array
102
+ * Ensures primary database is always in the databases array
103
+ */
104
+ private async migrateConfig(
105
+ config: ContainerConfig,
106
+ ): Promise<ContainerConfig> {
107
+ let needsSave = false
108
+
109
+ // If databases array is missing, create it with the primary database
110
+ if (!config.databases) {
111
+ config.databases = [config.database]
112
+ needsSave = true
113
+ }
114
+
115
+ // Ensure primary database is in the array
116
+ if (!config.databases.includes(config.database)) {
117
+ config.databases = [config.database, ...config.databases]
118
+ needsSave = true
119
+ }
120
+
121
+ // Save if we made changes
122
+ if (needsSave) {
123
+ await this.saveConfig(config.name, { engine: config.engine }, config)
124
+ }
125
+
126
+ return config
127
+ }
128
+
96
129
  /**
97
130
  * Save container configuration
98
131
  */
@@ -333,6 +366,49 @@ export class ContainerManager {
333
366
  return /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)
334
367
  }
335
368
 
369
+ /**
370
+ * Add a database to the container's databases array
371
+ */
372
+ async addDatabase(containerName: string, database: string): Promise<void> {
373
+ const config = await this.getConfig(containerName)
374
+ if (!config) {
375
+ throw new Error(`Container "${containerName}" not found`)
376
+ }
377
+
378
+ // Ensure databases array exists
379
+ if (!config.databases) {
380
+ config.databases = [config.database]
381
+ }
382
+
383
+ // Add if not already present
384
+ if (!config.databases.includes(database)) {
385
+ config.databases.push(database)
386
+ await this.saveConfig(containerName, { engine: config.engine }, config)
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Remove a database from the container's databases array
392
+ */
393
+ async removeDatabase(containerName: string, database: string): Promise<void> {
394
+ const config = await this.getConfig(containerName)
395
+ if (!config) {
396
+ throw new Error(`Container "${containerName}" not found`)
397
+ }
398
+
399
+ // Don't remove the primary database from the array
400
+ if (database === config.database) {
401
+ throw new Error(
402
+ `Cannot remove primary database "${database}" from tracking`,
403
+ )
404
+ }
405
+
406
+ if (config.databases) {
407
+ config.databases = config.databases.filter((db) => db !== database)
408
+ await this.saveConfig(containerName, { engine: config.engine }, config)
409
+ }
410
+ }
411
+
336
412
  /**
337
413
  * Get connection string for a container
338
414
  * Delegates to the appropriate engine
@@ -20,6 +20,7 @@ import {
20
20
  mycliDependency,
21
21
  } from '../config/os-dependencies'
22
22
  import { platformService } from './platform-service'
23
+ import { configManager } from './config-manager'
23
24
 
24
25
  const execAsync = promisify(exec)
25
26
 
@@ -268,6 +269,10 @@ export async function installDependency(
268
269
  execWithInheritedStdio(cmd)
269
270
  }
270
271
 
272
+ // Refresh config cache after package manager interaction
273
+ // This ensures newly installed tools are detected with correct versions
274
+ await configManager.refreshAllBinaries()
275
+
271
276
  // Verify installation
272
277
  const status = await checkDependency(dependency)
273
278
  if (!status.installed) {
@@ -0,0 +1,194 @@
1
+ import { exec } from 'child_process'
2
+ import { promisify } from 'util'
3
+ import { createRequire } from 'module'
4
+ import { configManager } from './config-manager'
5
+
6
+ const execAsync = promisify(exec)
7
+ const require = createRequire(import.meta.url)
8
+
9
+ const NPM_REGISTRY_URL = 'https://registry.npmjs.org/spindb'
10
+ const CHECK_THROTTLE_MS = 24 * 60 * 60 * 1000 // 24 hours
11
+
12
+ export type UpdateCheckResult = {
13
+ currentVersion: string
14
+ latestVersion: string
15
+ updateAvailable: boolean
16
+ lastChecked: string
17
+ }
18
+
19
+ export type UpdateResult = {
20
+ success: boolean
21
+ previousVersion: string
22
+ newVersion: string
23
+ error?: string
24
+ }
25
+
26
+ export class UpdateManager {
27
+ /**
28
+ * Get currently installed version from package.json
29
+ */
30
+ getCurrentVersion(): string {
31
+ const pkg = require('../package.json') as { version: string }
32
+ return pkg.version
33
+ }
34
+
35
+ /**
36
+ * Check npm registry for latest version
37
+ * Throttled to once per 24 hours unless force=true
38
+ */
39
+ async checkForUpdate(force = false): Promise<UpdateCheckResult | null> {
40
+ const config = await configManager.load()
41
+ const lastCheck = config.update?.lastCheck
42
+
43
+ // Return cached result if within throttle period
44
+ if (!force && lastCheck) {
45
+ const elapsed = Date.now() - new Date(lastCheck).getTime()
46
+ if (elapsed < CHECK_THROTTLE_MS && config.update?.latestVersion) {
47
+ const currentVersion = this.getCurrentVersion()
48
+ return {
49
+ currentVersion,
50
+ latestVersion: config.update.latestVersion,
51
+ updateAvailable:
52
+ this.compareVersions(config.update.latestVersion, currentVersion) >
53
+ 0,
54
+ lastChecked: lastCheck,
55
+ }
56
+ }
57
+ }
58
+
59
+ try {
60
+ const latestVersion = await this.fetchLatestVersion()
61
+ const currentVersion = this.getCurrentVersion()
62
+
63
+ // Update cache
64
+ config.update = {
65
+ ...config.update,
66
+ lastCheck: new Date().toISOString(),
67
+ latestVersion,
68
+ }
69
+ await configManager.save()
70
+
71
+ return {
72
+ currentVersion,
73
+ latestVersion,
74
+ updateAvailable:
75
+ this.compareVersions(latestVersion, currentVersion) > 0,
76
+ lastChecked: new Date().toISOString(),
77
+ }
78
+ } catch {
79
+ // Offline or registry error - return null
80
+ return null
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Perform self-update via npm
86
+ */
87
+ async performUpdate(): Promise<UpdateResult> {
88
+ const previousVersion = this.getCurrentVersion()
89
+
90
+ try {
91
+ // Execute npm install globally
92
+ await execAsync('npm install -g spindb@latest', { timeout: 60000 })
93
+
94
+ // Verify new version by checking what npm reports
95
+ const { stdout } = await execAsync('npm list -g spindb --json')
96
+ const npmData = JSON.parse(stdout) as {
97
+ dependencies?: { spindb?: { version?: string } }
98
+ }
99
+ const newVersion =
100
+ npmData.dependencies?.spindb?.version || previousVersion
101
+
102
+ return {
103
+ success: true,
104
+ previousVersion,
105
+ newVersion,
106
+ }
107
+ } catch (error) {
108
+ const message = error instanceof Error ? error.message : String(error)
109
+
110
+ // Detect permission issues
111
+ if (message.includes('EACCES') || message.includes('permission')) {
112
+ return {
113
+ success: false,
114
+ previousVersion,
115
+ newVersion: previousVersion,
116
+ error: 'Permission denied. Try: sudo npm install -g spindb@latest',
117
+ }
118
+ }
119
+
120
+ return {
121
+ success: false,
122
+ previousVersion,
123
+ newVersion: previousVersion,
124
+ error: message,
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Get cached update info (for showing notification without network call)
131
+ */
132
+ async getCachedUpdateInfo(): Promise<{
133
+ latestVersion?: string
134
+ autoCheckEnabled: boolean
135
+ }> {
136
+ const config = await configManager.load()
137
+ return {
138
+ latestVersion: config.update?.latestVersion,
139
+ autoCheckEnabled: config.update?.autoCheckEnabled !== false,
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Set whether auto-update checks are enabled
145
+ */
146
+ async setAutoCheckEnabled(enabled: boolean): Promise<void> {
147
+ const config = await configManager.load()
148
+ config.update = {
149
+ ...config.update,
150
+ autoCheckEnabled: enabled,
151
+ }
152
+ await configManager.save()
153
+ }
154
+
155
+ /**
156
+ * Fetch latest version from npm registry
157
+ */
158
+ private async fetchLatestVersion(): Promise<string> {
159
+ const controller = new AbortController()
160
+ const timeout = setTimeout(() => controller.abort(), 10000)
161
+
162
+ try {
163
+ const response = await fetch(NPM_REGISTRY_URL, {
164
+ signal: controller.signal,
165
+ })
166
+ if (!response.ok) {
167
+ throw new Error(`Registry returned ${response.status}`)
168
+ }
169
+ const data = (await response.json()) as {
170
+ 'dist-tags': { latest: string }
171
+ }
172
+ return data['dist-tags'].latest
173
+ } finally {
174
+ clearTimeout(timeout)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Compare semver versions
180
+ * Returns >0 if a > b, <0 if a < b, 0 if equal
181
+ */
182
+ compareVersions(a: string, b: string): number {
183
+ const partsA = a.split('.').map((n) => parseInt(n, 10) || 0)
184
+ const partsB = b.split('.').map((n) => parseInt(n, 10) || 0)
185
+
186
+ for (let i = 0; i < 3; i++) {
187
+ const diff = (partsA[i] || 0) - (partsB[i] || 0)
188
+ if (diff !== 0) return diff
189
+ }
190
+ return 0
191
+ }
192
+ }
193
+
194
+ export const updateManager = new UpdateManager()
@@ -2,6 +2,8 @@ import type {
2
2
  ContainerConfig,
3
3
  ProgressCallback,
4
4
  BackupFormat,
5
+ BackupOptions,
6
+ BackupResult,
5
7
  RestoreResult,
6
8
  DumpResult,
7
9
  StatusResult,
@@ -131,4 +133,22 @@ export abstract class BaseEngine {
131
133
  connectionString: string,
132
134
  outputPath: string,
133
135
  ): Promise<DumpResult>
136
+
137
+ /**
138
+ * Get the size of a database in bytes
139
+ * Returns null if the container is not running or size cannot be determined
140
+ */
141
+ abstract getDatabaseSize(container: ContainerConfig): Promise<number | null>
142
+
143
+ /**
144
+ * Create a backup of a database
145
+ * @param container - The container configuration
146
+ * @param outputPath - Path to write the backup file
147
+ * @param options - Backup options including database name and format
148
+ */
149
+ abstract backup(
150
+ container: ContainerConfig,
151
+ outputPath: string,
152
+ options: BackupOptions,
153
+ ): Promise<BackupResult>
134
154
  }