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.
- package/LICENSE +8 -0
- package/README.md +107 -826
- package/cli/commands/create.ts +5 -1
- package/cli/commands/engines.ts +256 -1
- package/cli/commands/menu/backup-handlers.ts +16 -0
- package/cli/commands/menu/container-handlers.ts +170 -17
- package/cli/commands/menu/engine-handlers.ts +6 -0
- package/cli/commands/menu/settings-handlers.ts +6 -0
- package/cli/commands/menu/shell-handlers.ts +74 -14
- package/cli/commands/menu/sql-handlers.ts +8 -50
- package/cli/commands/menu/validators.ts +8 -0
- package/cli/commands/users.ts +264 -0
- package/cli/constants.ts +8 -0
- package/cli/helpers.ts +140 -0
- package/cli/index.ts +2 -0
- package/cli/ui/prompts.ts +24 -20
- package/config/backup-formats.ts +28 -0
- package/config/engine-defaults.ts +26 -0
- package/config/engines-registry.ts +1 -0
- package/config/engines.json +50 -0
- package/config/engines.schema.json +6 -1
- package/core/base-binary-manager.ts +6 -1
- package/core/config-manager.ts +20 -0
- package/core/credential-manager.ts +257 -0
- package/core/dependency-manager.ts +5 -0
- package/core/docker-exporter.ts +30 -0
- package/core/error-handler.ts +19 -0
- package/engines/base-engine.ts +32 -1
- package/engines/clickhouse/index.ts +99 -3
- package/engines/cockroachdb/index.ts +69 -2
- package/engines/couchdb/index.ts +149 -1
- package/engines/ferretdb/README.md +4 -0
- package/engines/ferretdb/index.ts +342 -13
- package/engines/index.ts +8 -0
- package/engines/influxdb/README.md +180 -0
- package/engines/influxdb/api-client.ts +64 -0
- package/engines/influxdb/backup.ts +160 -0
- package/engines/influxdb/binary-manager.ts +110 -0
- package/engines/influxdb/binary-urls.ts +69 -0
- package/engines/influxdb/hostdb-releases.ts +23 -0
- package/engines/influxdb/index.ts +1227 -0
- package/engines/influxdb/restore.ts +417 -0
- package/engines/influxdb/version-maps.ts +75 -0
- package/engines/influxdb/version-validator.ts +128 -0
- package/engines/mariadb/index.ts +96 -1
- package/engines/meilisearch/index.ts +97 -1
- package/engines/mongodb/index.ts +82 -0
- package/engines/mysql/index.ts +105 -1
- package/engines/postgresql/index.ts +92 -0
- package/engines/qdrant/index.ts +107 -2
- package/engines/redis/index.ts +106 -12
- package/engines/surrealdb/index.ts +102 -2
- package/engines/typedb/backup.ts +167 -0
- package/engines/typedb/binary-manager.ts +200 -0
- package/engines/typedb/binary-urls.ts +38 -0
- package/engines/typedb/cli-utils.ts +210 -0
- package/engines/typedb/hostdb-releases.ts +118 -0
- package/engines/typedb/index.ts +1275 -0
- package/engines/typedb/restore.ts +377 -0
- package/engines/typedb/version-maps.ts +48 -0
- package/engines/typedb/version-validator.ts +127 -0
- package/engines/valkey/index.ts +70 -2
- package/package.json +4 -1
- 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
|
+
}
|