spindb 0.32.2 → 0.33.1

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.
@@ -0,0 +1,417 @@
1
+ /**
2
+ * InfluxDB restore module
3
+ * Supports SQL-based restore using InfluxDB's REST API
4
+ */
5
+
6
+ import { open, readFile as readFileAsync } from 'fs/promises'
7
+ import { existsSync, statSync } from 'fs'
8
+ import { logDebug } from '../../core/error-handler'
9
+ import { influxdbApiRequest } from './api-client'
10
+ import type { BackupFormat, RestoreResult } from '../../types'
11
+
12
+ /**
13
+ * Detect backup format from file
14
+ * InfluxDB uses SQL dump files
15
+ */
16
+ export async function detectBackupFormat(
17
+ filePath: string,
18
+ ): Promise<BackupFormat> {
19
+ if (!existsSync(filePath)) {
20
+ throw new Error(`Backup file not found: ${filePath}`)
21
+ }
22
+
23
+ const stats = statSync(filePath)
24
+
25
+ if (stats.isDirectory()) {
26
+ return {
27
+ format: 'unknown',
28
+ description: 'Directory found - InfluxDB uses single SQL dump files',
29
+ restoreCommand: 'InfluxDB requires a single .sql file for restore',
30
+ }
31
+ }
32
+
33
+ // Check file extension for .sql files
34
+ if (filePath.endsWith('.sql')) {
35
+ return {
36
+ format: 'sql',
37
+ description: 'InfluxDB SQL dump file',
38
+ restoreCommand:
39
+ 'Restore via InfluxDB REST API (spindb restore handles this)',
40
+ }
41
+ }
42
+
43
+ // Check file contents for SQL patterns
44
+ try {
45
+ const HEADER_SIZE = 4096
46
+ const buffer = Buffer.alloc(HEADER_SIZE)
47
+ const fd = await open(filePath, 'r')
48
+ let bytesRead: number
49
+ try {
50
+ const result = await fd.read(buffer, 0, HEADER_SIZE, 0)
51
+ bytesRead = result.bytesRead
52
+ } finally {
53
+ await fd.close().catch(() => {})
54
+ }
55
+
56
+ const content = buffer.toString('utf-8', 0, bytesRead)
57
+
58
+ // Check for InfluxDB-specific marker first
59
+ if (content.includes('-- InfluxDB SQL Backup')) {
60
+ return {
61
+ format: 'sql',
62
+ description: 'InfluxDB SQL dump file (detected by content)',
63
+ restoreCommand:
64
+ 'Restore via InfluxDB REST API (spindb restore handles this)',
65
+ }
66
+ }
67
+
68
+ // Fall back to generic SQL markers with a warning
69
+ if (content.includes('INSERT INTO') || content.includes('CREATE TABLE')) {
70
+ logDebug(
71
+ `Backup file "${filePath}" detected as SQL by generic markers (INSERT INTO / CREATE TABLE) — not the InfluxDB-specific header. Verify this is an InfluxDB backup.`,
72
+ )
73
+ return {
74
+ format: 'sql',
75
+ description:
76
+ 'SQL dump file (detected by generic markers, may not be InfluxDB-specific)',
77
+ restoreCommand:
78
+ 'Restore via InfluxDB REST API (spindb restore handles this)',
79
+ }
80
+ }
81
+ } catch (error) {
82
+ logDebug(`Error reading backup file header: ${error}`)
83
+ }
84
+
85
+ return {
86
+ format: 'unknown',
87
+ description: 'Unknown backup format',
88
+ restoreCommand: 'Use .sql file for restore',
89
+ }
90
+ }
91
+
92
+ // Restore options for InfluxDB
93
+ export type RestoreOptions = {
94
+ containerName: string
95
+ port: number
96
+ database: string
97
+ }
98
+
99
+ /**
100
+ * Parse SQL VALUES clause into an array of string values.
101
+ * Handles SQL string escaping ('' for embedded single quotes).
102
+ *
103
+ * NOTE: The `if (trimmed)` check on comma is intentional. Quoted strings push
104
+ * their value when the closing quote is encountered, leaving `current` empty.
105
+ * The subsequent comma separator sees that empty `current` and correctly skips
106
+ * it — it's a delimiter between quoted values, not an empty field. The backup
107
+ * code (backup.ts) never produces truly empty values (always NULL, numbers,
108
+ * booleans, or quoted strings), so this is safe. Do NOT change to always-push
109
+ * without also fixing the quoted-string-then-comma double-push problem.
110
+ */
111
+ function parseSqlValues(valuesStr: string): string[] {
112
+ const values: string[] = []
113
+ let current = ''
114
+ let inString = false
115
+
116
+ for (let i = 0; i < valuesStr.length; i++) {
117
+ const ch = valuesStr[i]
118
+
119
+ if (inString) {
120
+ if (ch === "'" && valuesStr[i + 1] === "'") {
121
+ // Escaped single quote
122
+ current += "'"
123
+ i++
124
+ } else if (ch === "'") {
125
+ // End of string
126
+ inString = false
127
+ values.push(current)
128
+ current = ''
129
+ } else {
130
+ current += ch
131
+ }
132
+ } else {
133
+ if (ch === "'") {
134
+ inString = true
135
+ current = ''
136
+ } else if (ch === ',') {
137
+ const trimmed = current.trim()
138
+ if (trimmed) {
139
+ values.push(trimmed)
140
+ }
141
+ current = ''
142
+ } else {
143
+ current += ch
144
+ }
145
+ }
146
+ }
147
+
148
+ const trimmed = current.trim()
149
+ if (trimmed) {
150
+ values.push(trimmed)
151
+ }
152
+
153
+ return values
154
+ }
155
+
156
+ /**
157
+ * Convert parsed INSERT data to InfluxDB line protocol format.
158
+ * Format: measurement,tag1=val1 field1="val1",field2="val2" timestamp_ns
159
+ *
160
+ * Tag columns (from backup metadata) become line protocol tags,
161
+ * remaining columns become fields. This preserves the original schema
162
+ * so that records with the same timestamp remain distinct.
163
+ */
164
+ /**
165
+ * Encode a SQL value as an InfluxDB line protocol field value.
166
+ * - Booleans: unquoted (true/false)
167
+ * - Integers: trailing 'i' (e.g. 123i) — only when the raw string has no
168
+ * decimal point or exponent, so "123.0" is preserved as a float
169
+ * - Floats: unquoted numeric (e.g. 3.14)
170
+ * - Strings: double-quoted with escaping
171
+ */
172
+ export function encodeFieldValue(val: string): string {
173
+ if (val === 'true' || val === 'false') {
174
+ return val
175
+ }
176
+
177
+ const num = Number(val)
178
+ if (!isNaN(num) && val !== '') {
179
+ if (Number.isInteger(num) && !/[.eE]/.test(val)) {
180
+ return `${num}i`
181
+ }
182
+ return `${num}`
183
+ }
184
+
185
+ const escaped = val.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
186
+ return `"${escaped}"`
187
+ }
188
+
189
+ function toLineProtocol(
190
+ table: string,
191
+ columns: string[],
192
+ values: string[],
193
+ tagColumns: Set<string>,
194
+ ): string | null {
195
+ const tags: string[] = []
196
+ const fields: string[] = []
197
+ let timestampNs = ''
198
+
199
+ for (let i = 0; i < columns.length; i++) {
200
+ const col = columns[i]
201
+ const val = values[i]
202
+
203
+ if (!val || val === 'NULL') continue
204
+
205
+ if (col === 'time') {
206
+ const ms = new Date(val).getTime()
207
+ if (!isNaN(ms)) {
208
+ timestampNs = String(ms * 1_000_000)
209
+ } else {
210
+ logDebug(`Warning: unparseable timestamp value "${val}" in restore`)
211
+ }
212
+ continue
213
+ }
214
+
215
+ if (tagColumns.has(col)) {
216
+ // Tags: key=value (no quotes, escape spaces/commas/equals)
217
+ const escaped = val
218
+ .replace(/\\/g, '\\\\')
219
+ .replace(/ /g, '\\ ')
220
+ .replace(/,/g, '\\,')
221
+ .replace(/=/g, '\\=')
222
+ tags.push(`${col}=${escaped}`)
223
+ } else {
224
+ fields.push(`${col}=${encodeFieldValue(val)}`)
225
+ }
226
+ }
227
+
228
+ if (fields.length === 0) return null
229
+
230
+ let line = table
231
+ if (tags.length > 0) {
232
+ line += `,${tags.join(',')}`
233
+ }
234
+ line += ` ${fields.join(',')}`
235
+ if (timestampNs) {
236
+ line += ` ${timestampNs}`
237
+ }
238
+ return line
239
+ }
240
+
241
+ /**
242
+ * Restore from SQL backup by parsing INSERT statements, converting to
243
+ * line protocol, and writing via the write_lp endpoint.
244
+ *
245
+ * InfluxDB 3.x does not support INSERT via the query_sql endpoint —
246
+ * data writes must go through /api/v3/write_lp.
247
+ */
248
+ async function restoreSqlBackup(
249
+ backupPath: string,
250
+ options: RestoreOptions,
251
+ ): Promise<RestoreResult> {
252
+ const { port, database } = options
253
+
254
+ logDebug(
255
+ `Restoring SQL backup to InfluxDB on port ${port}, database ${database}`,
256
+ )
257
+
258
+ const content = await readFileAsync(backupPath, 'utf-8')
259
+
260
+ // Parse tag metadata from backup comments (-- Tags: col1, col2)
261
+ const tagsByTable = new Map<string, Set<string>>()
262
+ const tagsRegex = /-- Table: (\S+)\r?\n-- Tags: (.+)/g
263
+ let tagsMatch
264
+ while ((tagsMatch = tagsRegex.exec(content)) !== null) {
265
+ const table = tagsMatch[1]
266
+ const tags = new Set(tagsMatch[2].split(',').map((t) => t.trim()))
267
+ tagsByTable.set(table, tags)
268
+ }
269
+
270
+ // Parse INSERT INTO statements and convert to line protocol
271
+ const insertRegex =
272
+ /INSERT INTO "([^"]+)"\s*\(([^)]+)\)\s*VALUES\s*\((.+)\);/g
273
+ const linesByTable = new Map<string, string[]>()
274
+
275
+ let match
276
+ while ((match = insertRegex.exec(content)) !== null) {
277
+ const table = match[1]
278
+ const columns = match[2].split(',').map((c) => c.trim().replace(/"/g, ''))
279
+ const values = parseSqlValues(match[3])
280
+ const tableTags = tagsByTable.get(table) ?? new Set<string>()
281
+ const line = toLineProtocol(table, columns, values, tableTags)
282
+
283
+ if (line) {
284
+ if (!linesByTable.has(table)) linesByTable.set(table, [])
285
+ linesByTable.get(table)!.push(line)
286
+ }
287
+ }
288
+
289
+ logDebug(
290
+ `Parsed ${[...linesByTable.values()].reduce((sum, l) => sum + l.length, 0)} records from ${linesByTable.size} tables`,
291
+ )
292
+
293
+ let totalRecords = 0
294
+ const errors: string[] = []
295
+
296
+ for (const [table, lines] of linesByTable) {
297
+ const body = lines.join('\n')
298
+ try {
299
+ const response = await influxdbApiRequest(
300
+ port,
301
+ 'POST',
302
+ `/api/v3/write_lp?db=${encodeURIComponent(database)}`,
303
+ body,
304
+ )
305
+
306
+ if (response.status < 300) {
307
+ totalRecords += lines.length
308
+ } else {
309
+ errors.push(
310
+ `Failed to write ${table}: ${JSON.stringify(response.data)}`,
311
+ )
312
+ logDebug(
313
+ `write_lp error for ${table}: ${JSON.stringify(response.data)}`,
314
+ )
315
+ }
316
+ } catch (error) {
317
+ errors.push(
318
+ `Error writing ${table}: ${error instanceof Error ? error.message : String(error)}`,
319
+ )
320
+ }
321
+ }
322
+
323
+ const message =
324
+ `Restored ${totalRecords} records from ${linesByTable.size} tables` +
325
+ (errors.length > 0 ? `. ${errors.length} errors.` : '')
326
+
327
+ return {
328
+ format: 'sql',
329
+ stdout: message,
330
+ code: errors.length > 0 ? 1 : 0,
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Restore from backup
336
+ * Supports SQL format
337
+ */
338
+ export async function restoreBackup(
339
+ backupPath: string,
340
+ options: RestoreOptions,
341
+ ): Promise<RestoreResult> {
342
+ if (!existsSync(backupPath)) {
343
+ throw new Error(`Backup file not found: ${backupPath}`)
344
+ }
345
+
346
+ // Detect backup format
347
+ const format = await detectBackupFormat(backupPath)
348
+ logDebug(`Detected backup format: ${format.format}`)
349
+
350
+ if (format.format === 'sql') {
351
+ return restoreSqlBackup(backupPath, options)
352
+ }
353
+
354
+ throw new Error(
355
+ `Invalid backup format: ${format.format}. Use .sql file for restore.`,
356
+ )
357
+ }
358
+
359
+ /**
360
+ * Parse InfluxDB connection string
361
+ * Format: http://host[:port], https://host[:port], or influxdb://host[:port]
362
+ *
363
+ * The influxdb:// scheme is an alias for http://
364
+ */
365
+ export function parseConnectionString(connectionString: string): {
366
+ host: string
367
+ port: number
368
+ protocol: 'http' | 'https'
369
+ database?: string
370
+ } {
371
+ if (!connectionString || typeof connectionString !== 'string') {
372
+ throw new Error(
373
+ 'Invalid InfluxDB connection string: expected a non-empty string',
374
+ )
375
+ }
376
+
377
+ // Handle influxdb:// scheme
378
+ let normalized = connectionString.trim()
379
+ if (normalized.startsWith('influxdb://')) {
380
+ normalized = normalized.replace('influxdb://', 'http://')
381
+ }
382
+
383
+ let url: URL
384
+ try {
385
+ url = new URL(normalized)
386
+ } catch (error) {
387
+ throw new Error(
388
+ `Invalid InfluxDB connection string: "${connectionString}". ` +
389
+ `Expected format: http://host[:port] or influxdb://host[:port]`,
390
+ { cause: error },
391
+ )
392
+ }
393
+
394
+ // Validate protocol
395
+ let protocol: 'http' | 'https'
396
+ if (url.protocol === 'http:') {
397
+ protocol = 'http'
398
+ } else if (url.protocol === 'https:') {
399
+ protocol = 'https'
400
+ } else {
401
+ throw new Error(
402
+ `Invalid InfluxDB connection string: unsupported protocol "${url.protocol}". ` +
403
+ `Expected "http://", "https://", or "influxdb://"`,
404
+ )
405
+ }
406
+
407
+ const host = url.hostname || '127.0.0.1'
408
+ const port = parseInt(url.port, 10) || 8086
409
+ const database = url.searchParams.get('db') || undefined
410
+
411
+ return {
412
+ host,
413
+ port,
414
+ protocol,
415
+ database,
416
+ }
417
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * InfluxDB Version Maps
3
+ *
4
+ * TEMPORARY: This version map will be replaced by the hostdb npm package once published.
5
+ * Until then, manually keep this in sync with robertjbass/hostdb releases.json:
6
+ * https://github.com/robertjbass/hostdb/blob/main/releases.json
7
+ *
8
+ * When updating versions:
9
+ * 1. Check hostdb releases.json for available versions
10
+ * 2. Update INFLUXDB_VERSION_MAP to match
11
+ */
12
+
13
+ import { logDebug } from '../../core/error-handler'
14
+
15
+ /**
16
+ * Map of major InfluxDB versions to their latest stable patch versions.
17
+ * Must match versions available in hostdb releases.json.
18
+ */
19
+ export const INFLUXDB_VERSION_MAP: Record<string, string> = {
20
+ // 1-part: major version -> latest
21
+ '3': '3.8.0',
22
+ // 2-part: major.minor -> latest patch
23
+ '3.8': '3.8.0',
24
+ // 3-part: exact version (identity mapping)
25
+ '3.8.0': '3.8.0',
26
+ }
27
+
28
+ /**
29
+ * Supported major InfluxDB versions (1-part format).
30
+ * Used for grouping and display purposes.
31
+ */
32
+ export const SUPPORTED_MAJOR_VERSIONS = ['3']
33
+
34
+ /**
35
+ * Get the full version string for a major version.
36
+ *
37
+ * @param majorVersion - Major version (e.g., '3')
38
+ * @returns Full version string (e.g., '3.8.0') or null if not supported
39
+ */
40
+ export function getFullVersion(majorVersion: string): string | null {
41
+ return INFLUXDB_VERSION_MAP[majorVersion] || null
42
+ }
43
+
44
+ /**
45
+ * Normalize a version string to X.Y.Z format.
46
+ *
47
+ * @param version - Version string (e.g., '3', '3.8', '3.8.0')
48
+ * @returns Normalized version (e.g., '3.8.0')
49
+ */
50
+ export function normalizeVersion(version: string): string {
51
+ // If it's in the version map (major, major.minor, or full version), return the mapped value
52
+ const fullVersion = INFLUXDB_VERSION_MAP[version]
53
+ if (fullVersion) {
54
+ return fullVersion
55
+ }
56
+
57
+ // Unknown version - warn and return as-is
58
+ const parts = version.split('.')
59
+
60
+ const isValidFormat =
61
+ parts.length >= 1 &&
62
+ parts.length <= 3 &&
63
+ parts.every((p) => /^\d+$/.test(p))
64
+
65
+ if (!isValidFormat) {
66
+ logDebug(
67
+ `InfluxDB version '${version}' has invalid format, may not be available in hostdb`,
68
+ )
69
+ } else {
70
+ logDebug(
71
+ `InfluxDB version '${version}' not in version map, may not be available in hostdb`,
72
+ )
73
+ }
74
+ return version
75
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * InfluxDB version validation utilities
3
+ * Handles version parsing, comparison, and compatibility checking
4
+ */
5
+
6
+ /**
7
+ * Parse an InfluxDB version string into components
8
+ * Handles formats like "3.8.0", "3.8", "v3.8.0"
9
+ */
10
+ export function parseVersion(versionString: string): {
11
+ major: number
12
+ minor: number
13
+ patch: number
14
+ raw: string
15
+ } | null {
16
+ const cleaned = versionString.replace(/^v/, '').trim()
17
+ const parts = cleaned.split('.')
18
+
19
+ if (parts.length < 1) return null
20
+
21
+ const major = parseInt(parts[0], 10)
22
+ const minor = parts[1] ? parseInt(parts[1], 10) : 0
23
+ const patch = parts[2] ? parseInt(parts[2], 10) : 0
24
+
25
+ if (isNaN(major)) return null
26
+ if (parts[1] && isNaN(minor)) return null
27
+ if (parts[2] && isNaN(patch)) return null
28
+
29
+ return { major, minor, patch, raw: cleaned }
30
+ }
31
+
32
+ /**
33
+ * Check if an InfluxDB version is supported by SpinDB
34
+ * Minimum supported version: 3.0.0 (InfluxDB 3.x is the Rust rewrite)
35
+ */
36
+ export function isVersionSupported(version: string): boolean {
37
+ const parsed = parseVersion(version)
38
+ if (!parsed) return false
39
+
40
+ return parsed.major >= 3
41
+ }
42
+
43
+ /**
44
+ * Get major version from full version string
45
+ * e.g., "3.8.0" -> "3"
46
+ */
47
+ export function getMajorVersion(version: string): string {
48
+ const parsed = parseVersion(version)
49
+ return parsed ? String(parsed.major) : version
50
+ }
51
+
52
+ /**
53
+ * Get major.minor version from full version string
54
+ * e.g., "3.8.0" -> "3.8"
55
+ */
56
+ export function getMajorMinorVersion(version: string): string {
57
+ const parsed = parseVersion(version)
58
+ if (!parsed) return version
59
+ return `${parsed.major}.${parsed.minor}`
60
+ }
61
+
62
+ /**
63
+ * Compare two InfluxDB versions
64
+ * Returns: -1 if a < b, 0 if a == b, 1 if a > b, null if either version cannot be parsed
65
+ */
66
+ export function compareVersions(a: string, b: string): number | null {
67
+ const parsedA = parseVersion(a)
68
+ const parsedB = parseVersion(b)
69
+
70
+ if (!parsedA || !parsedB) {
71
+ return null
72
+ }
73
+
74
+ if (parsedA.major !== parsedB.major) {
75
+ return parsedA.major < parsedB.major ? -1 : 1
76
+ }
77
+ if (parsedA.minor !== parsedB.minor) {
78
+ return parsedA.minor < parsedB.minor ? -1 : 1
79
+ }
80
+ if (parsedA.patch !== parsedB.patch) {
81
+ return parsedA.patch < parsedB.patch ? -1 : 1
82
+ }
83
+ return 0
84
+ }
85
+
86
+ /**
87
+ * Check if a backup version is compatible with the restore version
88
+ * InfluxDB 3.x backups are generally compatible within the same major version
89
+ */
90
+ export function isVersionCompatible(
91
+ backupVersion: string,
92
+ restoreVersion: string,
93
+ ): { compatible: boolean; warning?: string } {
94
+ const backup = parseVersion(backupVersion)
95
+ const restore = parseVersion(restoreVersion)
96
+
97
+ if (!backup || !restore) {
98
+ return {
99
+ compatible: true,
100
+ warning: 'Could not parse versions, proceeding with restore',
101
+ }
102
+ }
103
+
104
+ // Cannot restore newer backup to older server
105
+ if (backup.major > restore.major) {
106
+ return {
107
+ compatible: false,
108
+ warning: `Cannot restore InfluxDB ${backupVersion} backup to ${restoreVersion} server. The backup is from a newer major version.`,
109
+ }
110
+ }
111
+
112
+ // Allow same major version
113
+ if (backup.major === restore.major) {
114
+ return { compatible: true }
115
+ }
116
+
117
+ // Allow upgrading from older major version
118
+ return {
119
+ compatible: true,
120
+ warning: `Restoring InfluxDB ${backupVersion} backup to ${restoreVersion} server. Data format may be upgraded.`,
121
+ }
122
+ }
123
+
124
+ // Validate that a version string matches supported format
125
+ export function isValidVersionFormat(version: string): boolean {
126
+ const parsed = parseVersion(version)
127
+ return parsed !== null
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spindb",
3
- "version": "0.32.2",
3
+ "version": "0.33.1",
4
4
  "author": "Bob Bass <bob@bbass.co>",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "description": "Zero-config Docker-free local database containers. Create, backup, and clone a variety of popular databases.",
@@ -54,6 +54,7 @@
54
54
  "duckdb",
55
55
  "questdb",
56
56
  "typedb",
57
+ "influxdb",
57
58
  "tui",
58
59
  "cli",
59
60
  "package manager"
package/types/index.ts CHANGED
@@ -38,6 +38,7 @@ export enum Engine {
38
38
  SurrealDB = 'surrealdb',
39
39
  QuestDB = 'questdb',
40
40
  TypeDB = 'typedb',
41
+ InfluxDB = 'influxdb',
41
42
  }
42
43
 
43
44
  // Icon display mode for engine icons in CLI output
@@ -78,6 +79,7 @@ export const ALL_ENGINES = [
78
79
  Engine.SurrealDB,
79
80
  Engine.QuestDB,
80
81
  Engine.TypeDB,
82
+ Engine.InfluxDB,
81
83
  ] as const
82
84
 
83
85
  // File-based engines (no server process, data stored in user project directories)
@@ -208,6 +210,7 @@ export type CockroachDBFormat = 'sql'
208
210
  export type SurrealDBFormat = 'surql'
209
211
  export type QuestDBFormat = 'sql'
210
212
  export type TypeDBFormat = 'typeql'
213
+ export type InfluxDBFormat = 'sql'
211
214
 
212
215
  // Query command types
213
216
  export type QueryResultRow = Record<string, unknown>
@@ -286,6 +289,7 @@ export type BackupFormatType =
286
289
  | SurrealDBFormat
287
290
  | QuestDBFormat
288
291
  | TypeDBFormat
292
+ | InfluxDBFormat
289
293
 
290
294
  // Mapping from Engine to its corresponding backup format type
291
295
  type EngineFormatMap = {
@@ -306,6 +310,7 @@ type EngineFormatMap = {
306
310
  [Engine.SurrealDB]: SurrealDBFormat
307
311
  [Engine.QuestDB]: QuestDBFormat
308
312
  [Engine.TypeDB]: TypeDBFormat
313
+ [Engine.InfluxDB]: InfluxDBFormat
309
314
  }
310
315
 
311
316
  // Helper type to get format type for a specific engine
@@ -420,6 +425,8 @@ export type BinaryTool =
420
425
  // TypeDB tools
421
426
  | 'typedb'
422
427
  | 'typedb_console_bin'
428
+ // InfluxDB tools
429
+ | 'influxdb3'
423
430
  // Enhanced shells (optional)
424
431
  | 'pgcli'
425
432
  | 'mycli'
@@ -510,6 +517,8 @@ export type SpinDBConfig = {
510
517
  // TypeDB tools
511
518
  typedb?: BinaryConfig
512
519
  typedb_console_bin?: BinaryConfig
520
+ // InfluxDB tools
521
+ influxdb3?: BinaryConfig
513
522
  // Enhanced shells (optional)
514
523
  pgcli?: BinaryConfig
515
524
  mycli?: BinaryConfig