spindb 0.31.4 → 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.
Files changed (64) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +107 -826
  3. package/cli/commands/create.ts +5 -1
  4. package/cli/commands/engines.ts +256 -1
  5. package/cli/commands/menu/backup-handlers.ts +16 -0
  6. package/cli/commands/menu/container-handlers.ts +170 -17
  7. package/cli/commands/menu/engine-handlers.ts +6 -0
  8. package/cli/commands/menu/settings-handlers.ts +6 -0
  9. package/cli/commands/menu/shell-handlers.ts +74 -14
  10. package/cli/commands/menu/sql-handlers.ts +8 -50
  11. package/cli/commands/menu/validators.ts +8 -0
  12. package/cli/commands/users.ts +264 -0
  13. package/cli/constants.ts +8 -0
  14. package/cli/helpers.ts +140 -0
  15. package/cli/index.ts +2 -0
  16. package/cli/ui/prompts.ts +24 -20
  17. package/config/backup-formats.ts +28 -0
  18. package/config/engine-defaults.ts +26 -0
  19. package/config/engines-registry.ts +1 -0
  20. package/config/engines.json +50 -0
  21. package/config/engines.schema.json +6 -1
  22. package/core/base-binary-manager.ts +6 -1
  23. package/core/config-manager.ts +20 -0
  24. package/core/credential-manager.ts +257 -0
  25. package/core/dependency-manager.ts +5 -0
  26. package/core/docker-exporter.ts +30 -0
  27. package/core/error-handler.ts +19 -0
  28. package/engines/base-engine.ts +32 -1
  29. package/engines/clickhouse/index.ts +99 -3
  30. package/engines/cockroachdb/index.ts +69 -2
  31. package/engines/couchdb/index.ts +149 -1
  32. package/engines/ferretdb/README.md +4 -0
  33. package/engines/ferretdb/index.ts +342 -13
  34. package/engines/index.ts +8 -0
  35. package/engines/influxdb/README.md +180 -0
  36. package/engines/influxdb/api-client.ts +64 -0
  37. package/engines/influxdb/backup.ts +160 -0
  38. package/engines/influxdb/binary-manager.ts +110 -0
  39. package/engines/influxdb/binary-urls.ts +69 -0
  40. package/engines/influxdb/hostdb-releases.ts +23 -0
  41. package/engines/influxdb/index.ts +1227 -0
  42. package/engines/influxdb/restore.ts +417 -0
  43. package/engines/influxdb/version-maps.ts +75 -0
  44. package/engines/influxdb/version-validator.ts +128 -0
  45. package/engines/mariadb/index.ts +96 -1
  46. package/engines/meilisearch/index.ts +97 -1
  47. package/engines/mongodb/index.ts +82 -0
  48. package/engines/mysql/index.ts +105 -1
  49. package/engines/postgresql/index.ts +92 -0
  50. package/engines/qdrant/index.ts +107 -2
  51. package/engines/redis/index.ts +106 -12
  52. package/engines/surrealdb/index.ts +102 -2
  53. package/engines/typedb/backup.ts +167 -0
  54. package/engines/typedb/binary-manager.ts +200 -0
  55. package/engines/typedb/binary-urls.ts +38 -0
  56. package/engines/typedb/cli-utils.ts +210 -0
  57. package/engines/typedb/hostdb-releases.ts +118 -0
  58. package/engines/typedb/index.ts +1275 -0
  59. package/engines/typedb/restore.ts +377 -0
  60. package/engines/typedb/version-maps.ts +48 -0
  61. package/engines/typedb/version-validator.ts +127 -0
  62. package/engines/valkey/index.ts +70 -2
  63. package/package.json +4 -1
  64. package/types/index.ts +37 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * TypeDB Binary Manager
3
+ *
4
+ * Handles downloading, extracting, and managing TypeDB binaries from hostdb.
5
+ * Extends BaseBinaryManager for shared download/extraction logic.
6
+ *
7
+ * TypeDB archives extract to a `typedb/` directory with nested structure:
8
+ * typedb/
9
+ * ├── typedb (launcher script)
10
+ * ├── server/
11
+ * │ ├── typedb_server_bin (server binary)
12
+ * │ └── config.yml (default config)
13
+ * ├── console/
14
+ * │ └── typedb_console_bin (console binary)
15
+ * └── LICENSE
16
+ *
17
+ * We reorganize this preserving the relative structure the launcher expects:
18
+ * bin/
19
+ * ├── typedb (launcher)
20
+ * ├── server/
21
+ * │ └── typedb_server_bin (server binary)
22
+ * ├── console/
23
+ * │ └── typedb_console_bin (console binary)
24
+ * └── config.yml (moved for reference)
25
+ * server/
26
+ * └── config.yml (default config for reference)
27
+ */
28
+
29
+ import { existsSync } from 'fs'
30
+ import { mkdir, readdir } from 'fs/promises'
31
+ import { join } from 'path'
32
+ import {
33
+ BaseBinaryManager,
34
+ type BinaryManagerConfig,
35
+ } from '../../core/base-binary-manager'
36
+ import { moveEntry } from '../../core/fs-error-utils'
37
+ import { logDebug } from '../../core/error-handler'
38
+ import { paths } from '../../config/paths'
39
+ import { getBinaryUrl } from './binary-urls'
40
+ import { normalizeVersion } from './version-maps'
41
+ import { Engine, Platform, type Arch } from '../../types'
42
+
43
+ class TypeDBBinaryManager extends BaseBinaryManager {
44
+ protected readonly config: BinaryManagerConfig = {
45
+ engine: Engine.TypeDB,
46
+ engineName: 'typedb',
47
+ displayName: 'TypeDB',
48
+ serverBinary: 'typedb',
49
+ }
50
+
51
+ protected getBinaryUrlFromModule(
52
+ version: string,
53
+ platform: Platform,
54
+ arch: Arch,
55
+ ): string {
56
+ return getBinaryUrl(version, platform, arch)
57
+ }
58
+
59
+ protected normalizeVersionFromModule(version: string): string {
60
+ return normalizeVersion(version)
61
+ }
62
+
63
+ protected parseVersionFromOutput(stdout: string): string | null {
64
+ // Try standard three-part semver (e.g., "3.8.0")
65
+ const threePartMatch = stdout.match(/(\d+\.\d+\.\d+)/)
66
+ if (threePartMatch) {
67
+ return threePartMatch[1]
68
+ }
69
+
70
+ // Fallback: two-part version (e.g., "3.8")
71
+ const twoPartMatch = stdout.match(/(\d+\.\d+)/)
72
+ if (twoPartMatch) {
73
+ logDebug(
74
+ `TypeDB version parsed as two-part: ${twoPartMatch[1]} (from: ${stdout.trim().slice(0, 100)})`,
75
+ )
76
+ return twoPartMatch[1]
77
+ }
78
+
79
+ logDebug(
80
+ `Could not parse TypeDB version from output: ${stdout.trim().slice(0, 100)}`,
81
+ )
82
+ return null
83
+ }
84
+
85
+ /**
86
+ * Override moveExtractedEntries to handle TypeDB's nested directory structure.
87
+ *
88
+ * TypeDB archives extract to: typedb/server/typedb_server_bin, typedb/console/typedb_console_bin
89
+ * We reorganize to: bin/typedb, bin/server/typedb_server_bin, bin/console/typedb_console_bin
90
+ * And preserve server/config.yml for reference.
91
+ */
92
+ protected async moveExtractedEntries(
93
+ extractDir: string,
94
+ binPath: string,
95
+ ): Promise<void> {
96
+ const entries = await readdir(extractDir, { withFileTypes: true })
97
+ const ext = process.platform === 'win32' ? '.exe' : ''
98
+ const batExt = process.platform === 'win32' ? '.bat' : ''
99
+
100
+ // Find the typedb directory (e.g., "typedb" or "typedb-3.8.0")
101
+ const typedbDir = entries.find(
102
+ (e) =>
103
+ e.isDirectory() &&
104
+ (e.name === 'typedb' || e.name.startsWith('typedb-')),
105
+ )
106
+
107
+ const sourceDir = typedbDir ? join(extractDir, typedbDir.name) : extractDir
108
+
109
+ // Create bin/ directory
110
+ const destBinDir = join(binPath, 'bin')
111
+ await mkdir(destBinDir, { recursive: true })
112
+
113
+ // Move launcher script to bin/
114
+ const launcherName = `typedb${batExt}`
115
+ const launcherPath = join(sourceDir, launcherName)
116
+ if (existsSync(launcherPath)) {
117
+ await moveEntry(launcherPath, join(destBinDir, launcherName))
118
+ }
119
+
120
+ // Move server/ directory into bin/ (preserves bin/server/typedb_server_bin path the launcher expects)
121
+ const destServerDir = join(destBinDir, 'server')
122
+ await mkdir(destServerDir, { recursive: true })
123
+ const serverBinName = `typedb_server_bin${ext}`
124
+ const serverBinPath = join(sourceDir, 'server', serverBinName)
125
+ if (existsSync(serverBinPath)) {
126
+ await moveEntry(serverBinPath, join(destServerDir, serverBinName))
127
+ }
128
+
129
+ // Move console/ directory into bin/ (preserves bin/console/typedb_console_bin path the launcher expects)
130
+ const destConsoleDir = join(destBinDir, 'console')
131
+ await mkdir(destConsoleDir, { recursive: true })
132
+ const consoleBinName = `typedb_console_bin${ext}`
133
+ const consoleBinPath = join(sourceDir, 'console', consoleBinName)
134
+ if (existsSync(consoleBinPath)) {
135
+ await moveEntry(consoleBinPath, join(destConsoleDir, consoleBinName))
136
+ }
137
+
138
+ // Preserve server/config.yml as reference config
139
+ const configPath = join(sourceDir, 'server', 'config.yml')
140
+ if (existsSync(configPath)) {
141
+ const destRefServerDir = join(binPath, 'server')
142
+ await mkdir(destRefServerDir, { recursive: true })
143
+ await moveEntry(configPath, join(destRefServerDir, 'config.yml'))
144
+ }
145
+
146
+ // Move LICENSE if present
147
+ const licensePath = join(sourceDir, 'LICENSE')
148
+ if (existsSync(licensePath)) {
149
+ await moveEntry(licensePath, join(binPath, 'LICENSE'))
150
+ }
151
+
152
+ logDebug('TypeDB binaries reorganized to standard bin/ layout')
153
+ }
154
+
155
+ /**
156
+ * Override verify to handle TypeDB's launcher script.
157
+ * TypeDB's main binary is a launcher script, not a direct executable.
158
+ * We verify the actual server binary exists instead of running --version.
159
+ */
160
+ async verify(
161
+ version: string,
162
+ platform: Platform,
163
+ arch: Arch,
164
+ ): Promise<boolean> {
165
+ const fullVersion = this.getFullVersion(version)
166
+ const binPath = paths.getBinaryPath({
167
+ engine: this.config.engineName,
168
+ version: fullVersion,
169
+ platform,
170
+ arch,
171
+ })
172
+
173
+ const ext = platform === Platform.Win32 ? '.exe' : ''
174
+ const batExt = platform === Platform.Win32 ? '.bat' : ''
175
+ const launcherPath = join(binPath, 'bin', `typedb${batExt}`)
176
+ const serverPath = join(binPath, 'bin', 'server', `typedb_server_bin${ext}`)
177
+ const consolePath = join(
178
+ binPath,
179
+ 'bin',
180
+ 'console',
181
+ `typedb_console_bin${ext}`,
182
+ )
183
+
184
+ if (!existsSync(launcherPath)) {
185
+ throw new Error(`TypeDB launcher not found at ${launcherPath}`)
186
+ }
187
+
188
+ if (!existsSync(serverPath)) {
189
+ throw new Error(`TypeDB server binary not found at ${serverPath}`)
190
+ }
191
+
192
+ if (!existsSync(consolePath)) {
193
+ throw new Error(`TypeDB console binary not found at ${consolePath}`)
194
+ }
195
+
196
+ return true
197
+ }
198
+ }
199
+
200
+ export const typedbBinaryManager = new TypeDBBinaryManager()
@@ -0,0 +1,38 @@
1
+ /**
2
+ * TypeDB binary URL generation
3
+ *
4
+ * Generates download URLs for TypeDB binaries from hostdb.
5
+ */
6
+
7
+ import type { Platform, Arch } from '../../types'
8
+ import { normalizeVersion } from './version-maps'
9
+
10
+ const HOSTDB_BASE_URL =
11
+ 'https://github.com/robertjbass/hostdb/releases/download'
12
+
13
+ /**
14
+ * Get the binary download URL for a specific version and platform
15
+ *
16
+ * URL format: https://github.com/robertjbass/hostdb/releases/download/typedb-{version}/typedb-{version}-{platform}-{arch}.{ext}
17
+ *
18
+ * @param version - TypeDB version (e.g., '3.8.0' or '3')
19
+ * @param platform - Target platform (darwin, linux, win32)
20
+ * @param arch - Target architecture (x64, arm64)
21
+ */
22
+ export function getBinaryUrl(
23
+ version: string,
24
+ platform: Platform,
25
+ arch: Arch,
26
+ ): string {
27
+ const fullVersion = normalizeVersion(version)
28
+ const ext = getArchiveExtension(platform)
29
+
30
+ return `${HOSTDB_BASE_URL}/typedb-${fullVersion}/typedb-${fullVersion}-${platform}-${arch}.${ext}`
31
+ }
32
+
33
+ /**
34
+ * Get the archive extension for a platform
35
+ */
36
+ export function getArchiveExtension(platform: Platform): string {
37
+ return platform === 'win32' ? 'zip' : 'tar.gz'
38
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * TypeDB CLI utilities
3
+ *
4
+ * Helper functions for working with TypeDB command-line tools.
5
+ */
6
+
7
+ import { existsSync } from 'fs'
8
+ import { join } from 'path'
9
+ import { paths } from '../../config/paths'
10
+ import { configManager } from '../../core/config-manager'
11
+ import { platformService } from '../../core/platform-service'
12
+ import { normalizeVersion } from './version-maps'
13
+
14
+ export const TYPEDB_NOT_FOUND_ERROR =
15
+ 'TypeDB binary not found. Run: spindb engines download typedb <version>'
16
+
17
+ /** Default TypeDB credentials (TypeDB 3.x requires authentication) */
18
+ export const TYPEDB_DEFAULT_USERNAME = 'admin'
19
+ export const TYPEDB_DEFAULT_PASSWORD = 'password'
20
+
21
+ /**
22
+ * Get standard TypeDB console connection arguments including authentication.
23
+ * TypeDB 3.x requires --username and --password for all console operations.
24
+ *
25
+ * @param tlsDisabled - When true (the default for local dev), appends --tls-disabled.
26
+ * Pass false when connecting to a TLS-enabled TypeDB server.
27
+ */
28
+ export function getConsoleBaseArgs(
29
+ port: number,
30
+ host = '127.0.0.1',
31
+ tlsDisabled = true,
32
+ ): string[] {
33
+ const args = [
34
+ '--address',
35
+ `${host}:${port}`,
36
+ ...(tlsDisabled ? ['--tls-disabled'] : []),
37
+ '--username',
38
+ TYPEDB_DEFAULT_USERNAME,
39
+ '--password',
40
+ TYPEDB_DEFAULT_PASSWORD,
41
+ ]
42
+ return args
43
+ }
44
+
45
+ /**
46
+ * Get the path to the typedb launcher binary
47
+ *
48
+ * First checks the config cache, then scans the downloaded binaries directory.
49
+ * Returns null if not found.
50
+ */
51
+ export async function getTypeDBPath(): Promise<string | null> {
52
+ // Check config cache first
53
+ const cached = await configManager.getBinaryPath('typedb')
54
+ if (cached && existsSync(cached)) {
55
+ return cached
56
+ }
57
+
58
+ // Fall back to filesystem scan using the same logic as getTypeDBPathForVersion
59
+ const { TYPEDB_VERSION_MAP } = await import('./version-maps')
60
+ for (const version of Object.values(TYPEDB_VERSION_MAP)) {
61
+ const found = await getTypeDBPathForVersion(version)
62
+ if (found) {
63
+ await configManager.setBinaryPath('typedb', found, 'bundled')
64
+ return found
65
+ }
66
+ }
67
+
68
+ return null
69
+ }
70
+
71
+ /**
72
+ * Get the typedb binary path for a specific version
73
+ */
74
+ export async function getTypeDBPathForVersion(
75
+ version: string,
76
+ ): Promise<string | null> {
77
+ const { platform, arch } = platformService.getPlatformInfo()
78
+ const fullVersion = normalizeVersion(version)
79
+ // TypeDB launcher is a .bat script on Windows, no extension on other platforms
80
+ const batExt = platform === 'win32' ? '.bat' : ''
81
+
82
+ const binPath = paths.getBinaryPath({
83
+ engine: 'typedb',
84
+ version: fullVersion,
85
+ platform,
86
+ arch,
87
+ })
88
+
89
+ const typedbPath = join(binPath, 'bin', `typedb${batExt}`)
90
+ if (existsSync(typedbPath)) {
91
+ return typedbPath
92
+ }
93
+
94
+ return null
95
+ }
96
+
97
+ /**
98
+ * Get the typedb_console_bin path for a specific version
99
+ */
100
+ export async function getTypeDBConsolePath(
101
+ version: string,
102
+ ): Promise<string | null> {
103
+ const { platform, arch } = platformService.getPlatformInfo()
104
+ const fullVersion = normalizeVersion(version)
105
+ const ext = platformService.getExecutableExtension()
106
+
107
+ const binPath = paths.getBinaryPath({
108
+ engine: 'typedb',
109
+ version: fullVersion,
110
+ platform,
111
+ arch,
112
+ })
113
+
114
+ const consolePath = join(
115
+ binPath,
116
+ 'bin',
117
+ 'console',
118
+ `typedb_console_bin${ext}`,
119
+ )
120
+ if (existsSync(consolePath)) {
121
+ return consolePath
122
+ }
123
+
124
+ return null
125
+ }
126
+
127
+ /**
128
+ * Require the typedb binary path, throwing if not found
129
+ */
130
+ export async function requireTypeDBPath(version?: string): Promise<string> {
131
+ // If version provided, look for that specific version
132
+ if (version) {
133
+ const path = await getTypeDBPathForVersion(version)
134
+ if (path) {
135
+ return path
136
+ }
137
+ }
138
+
139
+ // Try config cache
140
+ const cached = await getTypeDBPath()
141
+ if (cached) {
142
+ return cached
143
+ }
144
+
145
+ throw new Error(TYPEDB_NOT_FOUND_ERROR)
146
+ }
147
+
148
+ /**
149
+ * Require the typedb_console_bin path, throwing if not found
150
+ */
151
+ export async function requireTypeDBConsolePath(
152
+ version?: string,
153
+ ): Promise<string> {
154
+ if (version) {
155
+ const path = await getTypeDBConsolePath(version)
156
+ if (path) {
157
+ return path
158
+ }
159
+ }
160
+
161
+ // Try config cache
162
+ const cached = await configManager.getBinaryPath('typedb_console_bin')
163
+ if (cached && existsSync(cached)) {
164
+ return cached
165
+ }
166
+
167
+ // Fall back to scanning all installed versions (same pattern as requireTypeDBPath)
168
+ const { TYPEDB_VERSION_MAP } = await import('./version-maps')
169
+ for (const ver of Object.values(TYPEDB_VERSION_MAP)) {
170
+ const found = await getTypeDBConsolePath(ver)
171
+ if (found) {
172
+ return found
173
+ }
174
+ }
175
+
176
+ throw new Error(
177
+ 'TypeDB console binary not found. Run: spindb engines download typedb <version>',
178
+ )
179
+ }
180
+
181
+ /**
182
+ * Validate a TypeDB identifier (database name)
183
+ * TypeDB identifiers follow specific rules:
184
+ * - Start with letter or underscore
185
+ * - Contain letters, digits, underscores, dashes
186
+ * - Max 63 characters
187
+ *
188
+ * @throws Error if identifier is invalid
189
+ */
190
+ export function validateTypeDBIdentifier(
191
+ identifier: string,
192
+ type: 'database' = 'database',
193
+ ): void {
194
+ if (!identifier) {
195
+ throw new Error(`${type} name cannot be empty`)
196
+ }
197
+
198
+ if (identifier.length > 63) {
199
+ throw new Error(`${type} name cannot exceed 63 characters`)
200
+ }
201
+
202
+ // TypeDB allows alphanumeric, underscores, and dashes
203
+ const validPattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/
204
+ if (!validPattern.test(identifier)) {
205
+ throw new Error(
206
+ `Invalid ${type} name "${identifier}". ` +
207
+ `Must start with a letter or underscore and contain only letters, digits, underscores, and dashes.`,
208
+ )
209
+ }
210
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * TypeDB hostdb releases integration
3
+ *
4
+ * Fetches available versions from hostdb releases.json and provides
5
+ * fallback to local version maps.
6
+ */
7
+
8
+ import { logDebug } from '../../core/error-handler'
9
+ import { TYPEDB_VERSION_MAP, SUPPORTED_MAJOR_VERSIONS } from './version-maps'
10
+
11
+ const HOSTDB_RELEASES_URL =
12
+ 'https://raw.githubusercontent.com/robertjbass/hostdb/main/releases.json'
13
+
14
+ // Cache for fetched versions (expires after 5 minutes)
15
+ let cachedVersions: Record<string, string[]> | null = null
16
+ let cacheExpiry = 0
17
+ const CACHE_TTL_MS = 5 * 60 * 1000
18
+
19
+ type HostdbReleases = {
20
+ [engine: string]: {
21
+ versions: {
22
+ version: string
23
+ platforms: string[]
24
+ }[]
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Fetch available TypeDB versions from hostdb
30
+ * Returns a map of major version to available patch versions
31
+ *
32
+ * Falls back to local version maps if fetch fails
33
+ */
34
+ export async function fetchAvailableVersions(): Promise<
35
+ Record<string, string[]>
36
+ > {
37
+ // Return cached versions if still valid
38
+ if (cachedVersions && Date.now() < cacheExpiry) {
39
+ return cachedVersions
40
+ }
41
+
42
+ try {
43
+ const controller = new AbortController()
44
+ const timeoutId = setTimeout(() => controller.abort(), 10000)
45
+ let response: Response
46
+ try {
47
+ response = await fetch(HOSTDB_RELEASES_URL, { signal: controller.signal })
48
+ } finally {
49
+ clearTimeout(timeoutId)
50
+ }
51
+ if (!response.ok) {
52
+ throw new Error(`HTTP ${response.status}`)
53
+ }
54
+
55
+ const releases = (await response.json()) as HostdbReleases
56
+ const typedbReleases = releases.typedb
57
+
58
+ if (!typedbReleases?.versions) {
59
+ throw new Error('No TypeDB versions found in releases.json')
60
+ }
61
+
62
+ // Group versions by major version
63
+ const versionMap: Record<string, string[]> = {}
64
+
65
+ for (const { version } of typedbReleases.versions) {
66
+ // Extract major version (e.g., "3" from "3.8.0")
67
+ const majorMatch = version.match(/^(\d+)/)
68
+ if (!majorMatch) continue
69
+
70
+ const majorVersion = majorMatch[1]
71
+ if (!versionMap[majorVersion]) {
72
+ versionMap[majorVersion] = []
73
+ }
74
+ versionMap[majorVersion].push(version)
75
+ }
76
+
77
+ // Sort versions within each major version (newest first)
78
+ for (const major of Object.keys(versionMap)) {
79
+ versionMap[major].sort((a, b) => {
80
+ const partsA = a.split('.').map(Number)
81
+ const partsB = b.split('.').map(Number)
82
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
83
+ const diff = (partsB[i] || 0) - (partsA[i] || 0)
84
+ if (diff !== 0) return diff
85
+ }
86
+ return 0
87
+ })
88
+ }
89
+
90
+ // Cache the results
91
+ cachedVersions = versionMap
92
+ cacheExpiry = Date.now() + CACHE_TTL_MS
93
+
94
+ logDebug('Fetched TypeDB versions from hostdb', { versionMap })
95
+ return versionMap
96
+ } catch (error) {
97
+ logDebug(`Failed to fetch hostdb releases: ${error}`)
98
+
99
+ // Fall back to local version maps
100
+ const fallbackMap: Record<string, string[]> = {}
101
+ for (const major of SUPPORTED_MAJOR_VERSIONS) {
102
+ const fullVersion = TYPEDB_VERSION_MAP[major]
103
+ if (fullVersion) {
104
+ fallbackMap[major] = [fullVersion]
105
+ }
106
+ }
107
+
108
+ return fallbackMap
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Clear the version cache (useful for testing)
114
+ */
115
+ export function clearVersionCache(): void {
116
+ cachedVersions = null
117
+ cacheExpiry = 0
118
+ }