spindb 0.34.5 → 0.35.3

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 (51) hide show
  1. package/README.md +4 -4
  2. package/cli/commands/create.ts +22 -1
  3. package/cli/commands/engines.ts +56 -22
  4. package/cli/commands/menu/container-handlers.ts +17 -1
  5. package/cli/commands/menu/engine-handlers.ts +48 -29
  6. package/cli/ui/theme.ts +5 -2
  7. package/config/engines-registry.ts +56 -0
  8. package/config/engines.json +14 -3
  9. package/config/engines.schema.json +13 -0
  10. package/core/base-binary-manager.ts +6 -2
  11. package/core/base-document-binary-manager.ts +5 -2
  12. package/core/base-embedded-binary-manager.ts +5 -2
  13. package/core/base-server-binary-manager.ts +5 -2
  14. package/core/hostdb-client.ts +157 -22
  15. package/core/hostdb-metadata.ts +67 -43
  16. package/engines/clickhouse/binary-urls.ts +1 -1
  17. package/engines/cockroachdb/binary-urls.ts +9 -7
  18. package/engines/cockroachdb/hostdb-releases.ts +18 -106
  19. package/engines/cockroachdb/version-maps.ts +1 -1
  20. package/engines/couchdb/binary-urls.ts +1 -1
  21. package/engines/duckdb/binary-urls.ts +1 -1
  22. package/engines/duckdb/index.ts +4 -74
  23. package/engines/ferretdb/README.md +76 -38
  24. package/engines/ferretdb/backup.ts +18 -10
  25. package/engines/ferretdb/binary-manager.ts +233 -35
  26. package/engines/ferretdb/binary-urls.ts +69 -24
  27. package/engines/ferretdb/index.ts +424 -213
  28. package/engines/ferretdb/restore.ts +23 -16
  29. package/engines/ferretdb/version-maps.ts +36 -8
  30. package/engines/index.ts +3 -4
  31. package/engines/influxdb/binary-urls.ts +1 -1
  32. package/engines/mariadb/binary-urls.ts +2 -2
  33. package/engines/meilisearch/binary-urls.ts +1 -1
  34. package/engines/mysql/binary-urls.ts +2 -2
  35. package/engines/postgresql/binary-urls.ts +1 -1
  36. package/engines/qdrant/binary-urls.ts +1 -1
  37. package/engines/questdb/binary-manager.ts +16 -9
  38. package/engines/questdb/binary-urls.ts +9 -10
  39. package/engines/questdb/hostdb-releases.ts +19 -97
  40. package/engines/questdb/version-maps.ts +2 -2
  41. package/engines/redis/binary-urls.ts +1 -8
  42. package/engines/sqlite/binary-urls.ts +1 -1
  43. package/engines/sqlite/index.ts +4 -74
  44. package/engines/surrealdb/binary-urls.ts +9 -7
  45. package/engines/surrealdb/hostdb-releases.ts +18 -106
  46. package/engines/surrealdb/version-maps.ts +1 -1
  47. package/engines/typedb/binary-urls.ts +10 -8
  48. package/engines/typedb/hostdb-releases.ts +18 -113
  49. package/engines/typedb/version-maps.ts +1 -1
  50. package/engines/valkey/binary-urls.ts +1 -1
  51. package/package.json +4 -1
@@ -1,15 +1,28 @@
1
1
  /**
2
2
  * Shared hostdb Client Module
3
3
  *
4
- * Provides centralized access to the hostdb repository at
5
- * https://github.com/robertjbass/hostdb
4
+ * Provides centralized access to pre-built database binaries.
5
+ * Primary registry: registry.layerbase.host
6
+ * Fallback registry: GitHub releases (robertjbass/hostdb)
6
7
  *
7
- * hostdb provides pre-built database binaries for multiple platforms.
8
8
  * This module handles fetching releases.json with caching to avoid
9
9
  * repeated network requests.
10
10
  */
11
11
 
12
12
  import { Platform, type Arch, type Engine } from '../types'
13
+ import { logDebug } from './error-handler'
14
+
15
+ // Registry base URLs
16
+ export const LAYERBASE_REGISTRY_BASE = 'https://registry.layerbase.host'
17
+ export const GITHUB_REGISTRY_BASE =
18
+ 'https://github.com/robertjbass/hostdb/releases/download'
19
+
20
+ /**
21
+ * Toggle GitHub fallback for binary downloads and releases.json fetches.
22
+ * Set to `false` to exclusively test the Layerbase registry.
23
+ * Must be re-enabled before release (see PRE_RELEASE_TASKS.md).
24
+ */
25
+ export const ENABLE_GITHUB_FALLBACK = false
13
26
 
14
27
  // Platform definition in hostdb releases.json
15
28
  export type HostdbPlatform = {
@@ -59,9 +72,21 @@ let cachedReleases: HostdbReleasesData | null = null
59
72
  let cacheTimestamp = 0
60
73
  const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
61
74
 
62
- const HOSTDB_RELEASES_URL =
75
+ export const LAYERBASE_RELEASES_URL =
76
+ 'https://registry.layerbase.host/releases.json'
77
+ export const GITHUB_RELEASES_URL =
63
78
  'https://raw.githubusercontent.com/robertjbass/hostdb/main/releases.json'
64
79
 
80
+ /**
81
+ * Get the list of releases.json URLs to try, respecting ENABLE_GITHUB_FALLBACK.
82
+ * When fallback is disabled, only the Layerbase URL is returned.
83
+ */
84
+ export function getReleasesUrls(): string[] {
85
+ return ENABLE_GITHUB_FALLBACK
86
+ ? [LAYERBASE_RELEASES_URL, GITHUB_RELEASES_URL]
87
+ : [LAYERBASE_RELEASES_URL]
88
+ }
89
+
65
90
  /**
66
91
  * Fetch releases.json from hostdb repository with caching.
67
92
  *
@@ -74,27 +99,39 @@ export async function fetchHostdbReleases(): Promise<HostdbReleasesData> {
74
99
  return cachedReleases
75
100
  }
76
101
 
77
- try {
78
- const response = await fetch(HOSTDB_RELEASES_URL, {
79
- signal: AbortSignal.timeout(5000),
80
- })
81
- if (!response.ok) {
82
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
83
- }
102
+ // Try layerbase registry first, fall back to GitHub (if enabled)
103
+ const urls = getReleasesUrls()
104
+ for (let i = 0; i < urls.length; i++) {
105
+ const url = urls[i]
106
+ const isLast = i === urls.length - 1
107
+ try {
108
+ const response = await fetch(url, {
109
+ signal: AbortSignal.timeout(5000),
110
+ })
111
+ if (!response.ok) {
112
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
113
+ }
84
114
 
85
- const data = (await response.json()) as HostdbReleasesData
115
+ const data = (await response.json()) as HostdbReleasesData
86
116
 
87
- // Cache the results
88
- cachedReleases = data
89
- cacheTimestamp = Date.now()
117
+ // Cache the results
118
+ cachedReleases = data
119
+ cacheTimestamp = Date.now()
90
120
 
91
- return data
92
- } catch (error) {
93
- const err = error as Error
94
- // Log the failure and rethrow - caller decides whether to use fallback
95
- console.warn(`Warning: Failed to fetch hostdb releases: ${err.message}`)
96
- throw error
121
+ return data
122
+ } catch (error) {
123
+ const err = error as Error
124
+ logDebug(`Failed to fetch releases from ${url}: ${err.message}`)
125
+ // If this was the last URL, rethrow
126
+ if (isLast) {
127
+ throw error
128
+ }
129
+ // Otherwise try the next URL
130
+ }
97
131
  }
132
+
133
+ // Should be unreachable (loop always throws on last iteration)
134
+ throw new Error('Failed to fetch hostdb releases from all registries')
98
135
  }
99
136
 
100
137
  /**
@@ -175,7 +212,105 @@ export function buildHostdbUrl(
175
212
  const tag = `${engine}-${version}`
176
213
  const filename = `${engine}-${version}-${hostdbPlatform}.${extension}`
177
214
 
178
- return `https://github.com/robertjbass/hostdb/releases/download/${tag}/${filename}`
215
+ return `${LAYERBASE_REGISTRY_BASE}/${tag}/${filename}`
216
+ }
217
+
218
+ /**
219
+ * Build a GitHub fallback URL for a hostdb release (same path scheme as layerbase).
220
+ */
221
+ export function buildGithubFallbackUrl(
222
+ engine: Engine | string,
223
+ options: BuildHostdbUrlOptions,
224
+ ): string {
225
+ const { version, hostdbPlatform, extension = 'tar.gz' } = options
226
+ const tag = `${engine}-${version}`
227
+ const filename = `${engine}-${version}-${hostdbPlatform}.${extension}`
228
+
229
+ return `${GITHUB_REGISTRY_BASE}/${tag}/${filename}`
230
+ }
231
+
232
+ /**
233
+ * Convert a layerbase registry URL to its GitHub fallback equivalent.
234
+ * Returns null if the URL is not a layerbase URL or if GitHub fallback is disabled.
235
+ */
236
+ export function getRegistryFallbackUrl(url: string): string | null {
237
+ if (!ENABLE_GITHUB_FALLBACK) return null
238
+ if (url.startsWith(LAYERBASE_REGISTRY_BASE)) {
239
+ return url.replace(LAYERBASE_REGISTRY_BASE, GITHUB_REGISTRY_BASE)
240
+ }
241
+ return null
242
+ }
243
+
244
+ /**
245
+ * Fetch wrapper that tries the primary URL first, then falls back to the
246
+ * GitHub registry if the primary is a layerbase URL and the request fails
247
+ * with a 404, 5xx, or network error.
248
+ *
249
+ * AbortError (timeout) is never retried — it propagates immediately.
250
+ */
251
+ export async function fetchWithRegistryFallback(
252
+ url: string,
253
+ options?: RequestInit,
254
+ ): Promise<Response> {
255
+ try {
256
+ const response = await fetch(url, options)
257
+ if (response.status === 404 || response.status >= 500) {
258
+ const fallbackUrl = getRegistryFallbackUrl(url)
259
+ if (fallbackUrl) {
260
+ logDebug(
261
+ `Primary registry returned ${response.status}, trying GitHub fallback`,
262
+ )
263
+ return await fetch(fallbackUrl, options)
264
+ }
265
+ }
266
+ return response
267
+ } catch (error) {
268
+ const err = error as Error
269
+ // Never retry on timeout (AbortError)
270
+ if (err.name === 'AbortError') {
271
+ throw error
272
+ }
273
+ const fallbackUrl = getRegistryFallbackUrl(url)
274
+ if (fallbackUrl) {
275
+ logDebug(
276
+ `Primary registry fetch failed (${err.message}), trying GitHub fallback`,
277
+ )
278
+ return await fetch(fallbackUrl, options)
279
+ }
280
+ throw error
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Try fetching from multiple registry URLs in order, returning the first
286
+ * successful (response.ok) Response. Logs per-URL failures via the
287
+ * supplied logger callback.
288
+ *
289
+ * @param urls - URLs to try in order (e.g., layerbase then GitHub)
290
+ * @param logger - Callback for logging per-URL failures
291
+ * @param timeoutMs - Per-request timeout in milliseconds (default: 5000)
292
+ * @returns The first successful Response
293
+ * @throws Error if all URLs fail
294
+ */
295
+ export async function fetchFromRegistryUrls(
296
+ urls: string[],
297
+ logger: (message: string) => void,
298
+ timeoutMs: number = 5000,
299
+ ): Promise<Response> {
300
+ let lastError: Error | null = null
301
+ for (const url of urls) {
302
+ try {
303
+ const response = await fetch(url, {
304
+ signal: AbortSignal.timeout(timeoutMs),
305
+ })
306
+ if (response.ok) return response
307
+ logger(`Registry fetch from ${url}: HTTP ${response.status}`)
308
+ } catch (error) {
309
+ logger(`Registry fetch from ${url} failed: ${error}`)
310
+ lastError = error as Error
311
+ }
312
+ }
313
+ throw lastError ?? new Error('All release registries failed')
179
314
  }
180
315
 
181
316
  export type BuildDownloadUrlOptions = {
@@ -2,15 +2,19 @@
2
2
  * Fetches metadata from hostdb (databases.json and downloads.json)
3
3
  * to understand what tools each engine needs and how to install them.
4
4
  *
5
+ * Primary registry: registry.layerbase.host
6
+ * Fallback registry: GitHub raw (robertjbass/hostdb)
7
+ *
5
8
  * Architecture:
6
9
  * - databases.json: Lists server, client, utilities, and enhanced CLI tools for each engine
7
10
  * - downloads.json: Provides package manager commands for installing tools
8
11
  */
9
12
 
10
13
  import { logDebug } from './error-handler'
14
+ import { LAYERBASE_REGISTRY_BASE } from './hostdb-client'
11
15
  import type { Engine } from '../types'
12
16
 
13
- const HOSTDB_RAW_BASE =
17
+ const GITHUB_RAW_BASE =
14
18
  'https://raw.githubusercontent.com/robertjbass/hostdb/main'
15
19
 
16
20
  const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
@@ -22,12 +26,29 @@ type CliTools = {
22
26
  enhanced: string[]
23
27
  }
24
28
 
29
+ export type VersionEntryObject = {
30
+ enabled?: boolean
31
+ platforms?: string[]
32
+ dependencies?: Array<{
33
+ database: string
34
+ cascadeDelete: boolean
35
+ note?: string
36
+ }>
37
+ cliTools?: CliTools
38
+ }
39
+
25
40
  type DatabaseEntry = {
26
41
  displayName: string
27
42
  cliTools: CliTools
28
- versions: Record<string, boolean> // version string -> available (true/false)
29
- latestLts: string
30
- // Other fields exist but we don't need them
43
+ versions: Record<string, boolean | VersionEntryObject>
44
+ platforms?: string[]
45
+ dependencies?: Array<{
46
+ database: string
47
+ cascadeDelete: boolean
48
+ note?: string
49
+ }>
50
+ spindbStatus?: string
51
+ hostedServiceAllowed?: boolean
31
52
  }
32
53
 
33
54
  type PackageManagerDef = {
@@ -69,7 +90,7 @@ let downloadsCache: { data: DownloadsJson; timestamp: number } | null = null
69
90
  const inFlightRequests = new Map<string, Promise<unknown>>()
70
91
 
71
92
  async function fetchWithCache<T>(
72
- url: string,
93
+ urls: string[],
73
94
  getCache: () => { data: T; timestamp: number } | null,
74
95
  setCache: (cache: { data: T; timestamp: number }) => void,
75
96
  ): Promise<T> {
@@ -80,35 +101,48 @@ async function fetchWithCache<T>(
80
101
  return cache.data
81
102
  }
82
103
 
83
- // Check for in-flight request to prevent duplicate fetches
84
- const inFlight = inFlightRequests.get(url)
104
+ // Check for in-flight request using the primary URL as key
105
+ const cacheKey = urls[0]
106
+ const inFlight = inFlightRequests.get(cacheKey)
85
107
  if (inFlight) {
86
108
  return inFlight as Promise<T>
87
109
  }
88
110
 
89
- // Create the fetch promise and store it
111
+ // Create the fetch promise try each URL in order
90
112
  const fetchPromise = (async () => {
91
113
  try {
92
- const response = await fetch(url)
93
- if (!response.ok) {
94
- throw new Error(`Failed to fetch ${url}: ${response.status}`)
114
+ let lastError: Error | null = null
115
+ for (const url of urls) {
116
+ try {
117
+ const response = await fetch(url)
118
+ if (!response.ok) {
119
+ throw new Error(`Failed to fetch ${url}: ${response.status}`)
120
+ }
121
+
122
+ const data = (await response.json()) as T
123
+ setCache({ data, timestamp: Date.now() })
124
+ return data
125
+ } catch (error) {
126
+ lastError = error as Error
127
+ logDebug(`Metadata fetch from ${url} failed: ${lastError.message}`)
128
+ }
95
129
  }
96
-
97
- const data = (await response.json()) as T
98
- setCache({ data, timestamp: Date.now() })
99
- return data
130
+ throw lastError ?? new Error('All metadata URLs failed')
100
131
  } finally {
101
- inFlightRequests.delete(url)
132
+ inFlightRequests.delete(cacheKey)
102
133
  }
103
134
  })()
104
135
 
105
- inFlightRequests.set(url, fetchPromise)
136
+ inFlightRequests.set(cacheKey, fetchPromise)
106
137
  return fetchPromise
107
138
  }
108
139
 
109
140
  export async function fetchDatabasesJson(): Promise<DatabasesJson> {
110
141
  return fetchWithCache(
111
- `${HOSTDB_RAW_BASE}/databases.json`,
142
+ [
143
+ `${LAYERBASE_REGISTRY_BASE}/databases.json`,
144
+ `${GITHUB_RAW_BASE}/databases.json`,
145
+ ],
112
146
  () => databasesCache,
113
147
  (c) => {
114
148
  databasesCache = c
@@ -118,7 +152,10 @@ export async function fetchDatabasesJson(): Promise<DatabasesJson> {
118
152
 
119
153
  export async function fetchDownloadsJson(): Promise<DownloadsJson> {
120
154
  return fetchWithCache(
121
- `${HOSTDB_RAW_BASE}/downloads.json`,
155
+ [
156
+ `${LAYERBASE_REGISTRY_BASE}/downloads.json`,
157
+ `${GITHUB_RAW_BASE}/downloads.json`,
158
+ ],
122
159
  () => downloadsCache,
123
160
  (c) => {
124
161
  downloadsCache = c
@@ -275,6 +312,16 @@ export async function getPackagesForTools(
275
312
  }
276
313
  }
277
314
 
315
+ /**
316
+ * Check if a version entry is enabled.
317
+ * Handles both old schema (boolean) and new schema (object with optional enabled field).
318
+ * Objects are enabled by default unless explicitly `{ enabled: false }`.
319
+ */
320
+ export function isVersionEnabled(value: boolean | VersionEntryObject): boolean {
321
+ if (typeof value === 'boolean') return value
322
+ return value.enabled !== false
323
+ }
324
+
278
325
  /**
279
326
  * Get available versions for a database engine from databases.json
280
327
  * This is the authoritative source for what versions are actually available in hostdb.
@@ -290,9 +337,8 @@ export async function getAvailableVersions(
290
337
  const entry = data[key]
291
338
  if (!entry?.versions) return null
292
339
 
293
- // Return only versions marked as available (true)
294
340
  return Object.entries(entry.versions)
295
- .filter(([, available]) => available)
341
+ .filter(([, value]) => isVersionEnabled(value))
296
342
  .map(([version]) => version)
297
343
  } catch (error) {
298
344
  logDebug('Failed to fetch available versions from hostdb', {
@@ -302,25 +348,3 @@ export async function getAvailableVersions(
302
348
  return null
303
349
  }
304
350
  }
305
-
306
- /**
307
- * Get the latest LTS version for a database engine
308
- * @param engine Engine (e.g., Engine.PostgreSQL or 'postgresql')
309
- * @returns Latest LTS version string, or null if not found
310
- */
311
- export async function getLatestLtsVersion(
312
- engine: Engine | string,
313
- ): Promise<string | null> {
314
- try {
315
- const data = await fetchDatabasesJson()
316
- const key = engine.toLowerCase()
317
- const entry = data[key]
318
- return entry?.latestLts || null
319
- } catch (error) {
320
- logDebug('Failed to fetch latest LTS version from hostdb', {
321
- engine,
322
- error: error instanceof Error ? error.message : String(error),
323
- })
324
- return null
325
- }
326
- }
@@ -35,7 +35,7 @@ export function getHostdbPlatform(
35
35
  /**
36
36
  * Build the download URL for ClickHouse binaries from hostdb
37
37
  *
38
- * Format: https://github.com/robertjbass/hostdb/releases/download/clickhouse-{version}/clickhouse-{version}-{platform}-{arch}.tar.gz
38
+ * Format: https://registry.layerbase.host/clickhouse-{version}/clickhouse-{version}-{platform}-{arch}.tar.gz
39
39
  *
40
40
  * @param version - ClickHouse version (e.g., '25.12', '25.12.3.21')
41
41
  * @param platform - Platform identifier (e.g., 'darwin', 'linux')
@@ -1,19 +1,17 @@
1
1
  /**
2
2
  * CockroachDB binary URL generation
3
3
  *
4
- * Generates download URLs for CockroachDB binaries from hostdb.
4
+ * Generates download URLs for CockroachDB binaries from the layerbase registry.
5
5
  */
6
6
 
7
- import type { Platform, Arch } from '../../types'
7
+ import { type Platform, type Arch, Engine } from '../../types'
8
8
  import { normalizeVersion } from './version-maps'
9
-
10
- const HOSTDB_BASE_URL =
11
- 'https://github.com/robertjbass/hostdb/releases/download'
9
+ import { buildHostdbUrl } from '../../core/hostdb-client'
12
10
 
13
11
  /**
14
12
  * Get the binary download URL for a specific version and platform
15
13
  *
16
- * URL format: https://github.com/robertjbass/hostdb/releases/download/cockroachdb-{version}/cockroachdb-{version}-{platform}-{arch}.{ext}
14
+ * URL format: https://registry.layerbase.host/cockroachdb-{version}/cockroachdb-{version}-{platform}-{arch}.{ext}
17
15
  *
18
16
  * @param version - CockroachDB version (e.g., '25.4.2' or '25')
19
17
  * @param platform - Target platform (darwin, linux, win32)
@@ -27,7 +25,11 @@ export function getBinaryUrl(
27
25
  const fullVersion = normalizeVersion(version)
28
26
  const ext = platform === 'win32' ? 'zip' : 'tar.gz'
29
27
 
30
- return `${HOSTDB_BASE_URL}/cockroachdb-${fullVersion}/cockroachdb-${fullVersion}-${platform}-${arch}.${ext}`
28
+ return buildHostdbUrl(Engine.CockroachDB, {
29
+ version: fullVersion,
30
+ hostdbPlatform: `${platform}-${arch}`,
31
+ extension: ext,
32
+ })
31
33
  }
32
34
 
33
35
  /**
@@ -1,114 +1,26 @@
1
1
  /**
2
- * CockroachDB hostdb releases integration
2
+ * hostdb Releases Module for CockroachDB
3
3
  *
4
- * Fetches available versions from hostdb releases.json and provides
5
- * fallback to local version maps.
4
+ * Fetches CockroachDB binary information from the hostdb repository at
5
+ * https://github.com/robertjbass/hostdb
6
6
  */
7
7
 
8
- import { logDebug } from '../../core/error-handler'
8
+ import { createHostdbReleases } from '../../core/hostdb-releases-factory'
9
9
  import {
10
10
  COCKROACHDB_VERSION_MAP,
11
11
  SUPPORTED_MAJOR_VERSIONS,
12
12
  } from './version-maps'
13
-
14
- const HOSTDB_RELEASES_URL =
15
- 'https://raw.githubusercontent.com/robertjbass/hostdb/main/releases.json'
16
-
17
- // Cache for fetched versions (expires after 5 minutes)
18
- let cachedVersions: Record<string, string[]> | null = null
19
- let cacheExpiry = 0
20
- const CACHE_TTL_MS = 5 * 60 * 1000
21
-
22
- type HostdbReleases = {
23
- [engine: string]: {
24
- versions: {
25
- version: string
26
- platforms: string[]
27
- }[]
28
- }
29
- }
30
-
31
- /**
32
- * Fetch available CockroachDB versions from hostdb
33
- * Returns a map of major version to available patch versions
34
- *
35
- * Falls back to local version maps if fetch fails
36
- */
37
- export async function fetchAvailableVersions(): Promise<
38
- Record<string, string[]>
39
- > {
40
- // Return cached versions if still valid
41
- if (cachedVersions && Date.now() < cacheExpiry) {
42
- return cachedVersions
43
- }
44
-
45
- try {
46
- const response = await fetch(HOSTDB_RELEASES_URL)
47
- if (!response.ok) {
48
- throw new Error(`HTTP ${response.status}`)
49
- }
50
-
51
- const releases = (await response.json()) as HostdbReleases
52
- const cockroachdbReleases = releases.cockroachdb
53
-
54
- if (!cockroachdbReleases?.versions) {
55
- throw new Error('No CockroachDB versions found in releases.json')
56
- }
57
-
58
- // Group versions by major version (YY.MM format)
59
- const versionMap: Record<string, string[]> = {}
60
-
61
- for (const { version } of cockroachdbReleases.versions) {
62
- // Extract major version (e.g., "25" from "25.4.2")
63
- const majorMatch = version.match(/^(\d+)/)
64
- if (!majorMatch) continue
65
-
66
- const majorVersion = majorMatch[1]
67
- if (!versionMap[majorVersion]) {
68
- versionMap[majorVersion] = []
69
- }
70
- versionMap[majorVersion].push(version)
71
- }
72
-
73
- // Sort versions within each major version (newest first)
74
- for (const major of Object.keys(versionMap)) {
75
- versionMap[major].sort((a, b) => {
76
- const partsA = a.split('.').map(Number)
77
- const partsB = b.split('.').map(Number)
78
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
79
- const diff = (partsB[i] || 0) - (partsA[i] || 0)
80
- if (diff !== 0) return diff
81
- }
82
- return 0
83
- })
84
- }
85
-
86
- // Cache the results
87
- cachedVersions = versionMap
88
- cacheExpiry = Date.now() + CACHE_TTL_MS
89
-
90
- logDebug('Fetched CockroachDB versions from hostdb', { versionMap })
91
- return versionMap
92
- } catch (error) {
93
- logDebug(`Failed to fetch hostdb releases: ${error}`)
94
-
95
- // Fall back to local version maps
96
- const fallbackMap: Record<string, string[]> = {}
97
- for (const major of SUPPORTED_MAJOR_VERSIONS) {
98
- const fullVersion = COCKROACHDB_VERSION_MAP[major]
99
- if (fullVersion) {
100
- fallbackMap[major] = [fullVersion]
101
- }
102
- }
103
-
104
- return fallbackMap
105
- }
106
- }
107
-
108
- /**
109
- * Clear the version cache (useful for testing)
110
- */
111
- export function clearVersionCache(): void {
112
- cachedVersions = null
113
- cacheExpiry = 0
114
- }
13
+ import { cockroachdbBinaryManager } from './binary-manager'
14
+ import { Engine } from '../../types'
15
+
16
+ const hostdbReleases = createHostdbReleases({
17
+ engine: Engine.CockroachDB,
18
+ displayName: 'CockroachDB',
19
+ versionMap: COCKROACHDB_VERSION_MAP,
20
+ supportedMajorVersions: SUPPORTED_MAJOR_VERSIONS,
21
+ groupingStrategy: 'single-digit',
22
+ listInstalled: () => cockroachdbBinaryManager.listInstalled(),
23
+ })
24
+
25
+ export const fetchAvailableVersions = hostdbReleases.fetchAvailableVersions
26
+ export const getLatestVersion = hostdbReleases.getLatestVersion
@@ -2,7 +2,7 @@
2
2
  * CockroachDB version mapping
3
3
  *
4
4
  * Maps short version aliases to full versions from hostdb releases.
5
- * MUST stay in sync with hostdb releases.json
5
+ * MUST stay in sync with hostdb databases.json
6
6
  */
7
7
 
8
8
  // Full version map for CockroachDB
@@ -37,7 +37,7 @@ export function getHostdbPlatform(
37
37
  /**
38
38
  * Build the download URL for CouchDB binaries from hostdb
39
39
  *
40
- * Format: https://github.com/robertjbass/hostdb/releases/download/couchdb-{version}/couchdb-{version}-{platform}-{arch}.{ext}
40
+ * Format: https://registry.layerbase.host/couchdb-{version}/couchdb-{version}-{platform}-{arch}.{ext}
41
41
  *
42
42
  * @param version - CouchDB version (e.g., '3', '3.5.1')
43
43
  * @param platform - Platform identifier (e.g., 'darwin', 'linux', 'win32')
@@ -12,7 +12,7 @@ import { Engine, type Platform, type Arch } from '../../types'
12
12
  /**
13
13
  * Build the download URL for DuckDB binaries from hostdb
14
14
  *
15
- * Format: https://github.com/robertjbass/hostdb/releases/download/duckdb-{version}/duckdb-{version}-{platform}-{arch}.{ext}
15
+ * Format: https://registry.layerbase.host/duckdb-{version}/duckdb-{version}-{platform}-{arch}.{ext}
16
16
  *
17
17
  * @param version - DuckDB version (e.g., '1', '1.4.3')
18
18
  * @param platform - Platform identifier (e.g., 'darwin', 'linux', 'win32')