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
package/README.md CHANGED
@@ -38,7 +38,7 @@ SpinDB supports **18 database engines** across **5 platform architectures**β€”al
38
38
  | πŸͺΆ **SQLite** | Embedded SQL | βœ… | βœ… | βœ… | βœ… | βœ… |
39
39
  | πŸ¦† **DuckDB** | Embedded OLAP | βœ… | βœ… | βœ… | βœ… | βœ… |
40
40
  | πŸƒ **MongoDB** | Document Store | βœ… | βœ… | βœ… | βœ… | βœ… |
41
- | πŸ¦” **FerretDB** | Document Store | βœ… | βœ… | βœ… | βœ… | ❌ |
41
+ | πŸ¦” **FerretDB** | Document Store | βœ… | βœ… | βœ… | βœ… | ⚠️ |
42
42
  | πŸ”΄ **Redis** | Key-Value | βœ… | βœ… | βœ… | βœ… | βœ… |
43
43
  | πŸ”· **Valkey** | Key-Value | βœ… | βœ… | βœ… | βœ… | βœ… |
44
44
  | 🏠 **ClickHouse** | Columnar OLAP | βœ… | βœ… | βœ… | βœ… | ❌ |
@@ -51,9 +51,9 @@ SpinDB supports **18 database engines** across **5 platform architectures**β€”al
51
51
  | πŸ€– **TypeDB** | Knowledge Graph | βœ… | βœ… | βœ… | βœ… | βœ… |
52
52
  | πŸ“ˆ **InfluxDB** | Time-Series | βœ… | βœ… | βœ… | βœ… | βœ… |
53
53
 
54
- **88 combinations. One CLI. Zero configuration.**
54
+ **89 combinations. One CLI. Zero configuration.**
55
55
 
56
- > ClickHouse and FerretDB are available on Windows via WSL.
56
+ > ClickHouse is available on Windows via WSL. FerretDB v1 is natively supported on Windows (uses plain PostgreSQL backend); v2 requires macOS/Linux.
57
57
 
58
58
  ---
59
59
 
@@ -272,7 +272,7 @@ See [DEPLOY.md](DEPLOY.md) for comprehensive deployment documentation.
272
272
 
273
273
  - **Local only** - Databases bind to `127.0.0.1`. Remote connection support planned for v1.1.
274
274
  - **ClickHouse Windows** - Not supported (hostdb doesn't build for Windows).
275
- - **FerretDB Windows** - Not supported (postgresql-documentdb has startup issues on Windows).
275
+ - **FerretDB Windows** - v1 supported natively (plain PostgreSQL backend). v2 not supported (postgresql-documentdb has startup issues); use WSL for v2.
276
276
  - **Qdrant, Meilisearch, CouchDB** - Use REST API instead of CLI shell. Access via HTTP at the configured port.
277
277
 
278
278
  ---
@@ -22,7 +22,11 @@ import { startWithRetry } from '../../core/start-with-retry'
22
22
  import { TransactionManager } from '../../core/transaction-manager'
23
23
  import { isValidDatabaseName, exitWithError } from '../../core/error-handler'
24
24
  import { resolve } from 'path'
25
- import { Engine } from '../../types'
25
+ import { Engine, Platform } from '../../types'
26
+ import {
27
+ FERRETDB_VERSION_MAP,
28
+ isV1 as isFerretDBv1,
29
+ } from '../../engines/ferretdb/version-maps'
26
30
  import type { BaseEngine } from '../../engines/base-engine'
27
31
 
28
32
  /**
@@ -535,6 +539,23 @@ export const createCommand = new Command('create')
535
539
  database = answers.database
536
540
  }
537
541
 
542
+ // FerretDB: force v1 on Windows (v2 requires postgresql-documentdb, not available on Windows)
543
+ // Runs after both CLI and interactive paths have resolved engine + version
544
+ if (
545
+ engine === Engine.FerretDB &&
546
+ !isFerretDBv1(version) &&
547
+ platformService.getPlatformInfo().platform === Platform.Win32
548
+ ) {
549
+ version = FERRETDB_VERSION_MAP['1']
550
+ if (!options.json) {
551
+ console.log(
552
+ chalk.yellow(
553
+ ` FerretDB v2 is not supported on Windows β€” using v1 (${version})`,
554
+ ),
555
+ )
556
+ }
557
+ }
558
+
538
559
  // Redis/Valkey use numbered databases (0-15), default to "0"
539
560
  // Other engines default to container name (with hyphens replaced by underscores for SQL compatibility)
540
561
  if (engine === Engine.Redis || engine === Engine.Valkey) {
@@ -53,6 +53,7 @@ import {
53
53
  import { Engine, Platform } from '../../types'
54
54
  import {
55
55
  loadEnginesJson,
56
+ filterEnginesByPlatform,
56
57
  type EngineConfig,
57
58
  } from '../../config/engines-registry'
58
59
  import { mysqlBinaryManager } from '../../engines/mysql/binary-manager'
@@ -74,7 +75,9 @@ import { typedbBinaryManager } from '../../engines/typedb/binary-manager'
74
75
  import { influxdbBinaryManager } from '../../engines/influxdb/binary-manager'
75
76
  import {
76
77
  DEFAULT_DOCUMENTDB_VERSION,
78
+ DEFAULT_V1_POSTGRESQL_VERSION,
77
79
  normalizeDocumentDBVersion,
80
+ isV1,
78
81
  } from '../../engines/ferretdb/version-maps'
79
82
 
80
83
  // Display manual installation instructions for missing dependencies
@@ -1727,26 +1730,41 @@ enginesCommand
1727
1730
  }
1728
1731
 
1729
1732
  if (['ferretdb', 'ferret'].includes(normalizedEngine)) {
1730
- // Check platform support
1731
1733
  const { platform } = platformService.getPlatformInfo()
1732
- if (platform === Platform.Win32) {
1734
+
1735
+ if (!version) {
1736
+ // Auto-select v1 on Windows (v2 not supported)
1737
+ if (platform === Platform.Win32) {
1738
+ version = '1'
1739
+ console.log(
1740
+ chalk.gray(
1741
+ ' Auto-selecting FerretDB v1 (v2 is not available on Windows)',
1742
+ ),
1743
+ )
1744
+ } else {
1745
+ console.error(uiError('FerretDB requires a version (e.g., 1 or 2)'))
1746
+ process.exit(1)
1747
+ }
1748
+ }
1749
+
1750
+ // Block v2 on Windows with helpful message
1751
+ if (platform === Platform.Win32 && !isV1(version)) {
1733
1752
  console.error(
1734
- uiError('FerretDB is not supported on Windows via hostdb'),
1753
+ uiError(
1754
+ 'FerretDB v2 is not supported on Windows (postgresql-documentdb has startup issues)',
1755
+ ),
1735
1756
  )
1736
1757
  console.log(
1737
1758
  chalk.gray(
1738
- ' FerretDB binaries are only available for macOS and Linux.',
1759
+ ' Use FerretDB v1 instead, which uses plain PostgreSQL:',
1739
1760
  ),
1740
1761
  )
1741
- process.exit(1)
1742
- }
1743
-
1744
- if (!version) {
1745
- console.error(uiError('FerretDB requires a version (e.g., 2)'))
1762
+ console.log(chalk.cyan(' spindb engines download ferretdb 1'))
1746
1763
  process.exit(1)
1747
1764
  }
1748
1765
 
1749
1766
  const engine = getEngine(Engine.FerretDB)
1767
+ const v1 = isV1(version)
1750
1768
 
1751
1769
  const spinner = createSpinner(
1752
1770
  `Checking FerretDB ${version} binaries...`,
@@ -1780,18 +1798,31 @@ enginesCommand
1780
1798
  )
1781
1799
  console.log(chalk.gray(` FerretDB location: ${binPath}`))
1782
1800
 
1783
- // Also show postgresql-documentdb location
1784
- const fullDocumentDBVersion = normalizeDocumentDBVersion(
1785
- DEFAULT_DOCUMENTDB_VERSION,
1786
- )
1787
- const documentdbPath = ferretdbBinaryManager.getDocumentDBBinaryPath(
1788
- fullDocumentDBVersion,
1789
- ferretPlatform,
1790
- ferretArch,
1791
- )
1792
- console.log(
1793
- chalk.gray(` postgresql-documentdb location: ${documentdbPath}`),
1794
- )
1801
+ // Show backend location (version-dependent)
1802
+ if (v1) {
1803
+ const pgFullVersion = postgresqlBinaryManager.getFullVersion(
1804
+ DEFAULT_V1_POSTGRESQL_VERSION,
1805
+ )
1806
+ const pgPath = paths.getBinaryPath({
1807
+ engine: 'postgresql',
1808
+ version: pgFullVersion,
1809
+ platform: ferretPlatform,
1810
+ arch: ferretArch,
1811
+ })
1812
+ console.log(chalk.gray(` PostgreSQL backend location: ${pgPath}`))
1813
+ } else {
1814
+ const fullDocumentDBVersion = normalizeDocumentDBVersion(
1815
+ DEFAULT_DOCUMENTDB_VERSION,
1816
+ )
1817
+ const documentdbPath = ferretdbBinaryManager.getDocumentDBBinaryPath(
1818
+ fullDocumentDBVersion,
1819
+ ferretPlatform,
1820
+ ferretArch,
1821
+ )
1822
+ console.log(
1823
+ chalk.gray(` postgresql-documentdb location: ${documentdbPath}`),
1824
+ )
1825
+ }
1795
1826
 
1796
1827
  // Skip client tools check - FerretDB uses MongoDB client tools (mongosh)
1797
1828
  // which are installed separately via: spindb engines download mongodb
@@ -2126,7 +2157,10 @@ enginesCommand
2126
2157
  .option('--all', 'Include pending and planned engines')
2127
2158
  .action(async (options: { json?: boolean; all?: boolean }) => {
2128
2159
  try {
2129
- const enginesData = await loadEnginesJson()
2160
+ const rawData = await loadEnginesJson()
2161
+ const { platform, arch } = platformService.getPlatformInfo()
2162
+ const platformKey = `${platform}-${arch}`
2163
+ const enginesData = filterEnginesByPlatform(rawData, platformKey)
2130
2164
 
2131
2165
  if (options.json) {
2132
2166
  // Output full JSON
@@ -377,7 +377,23 @@ export async function handleCreate(): Promise<'main' | string | void> {
377
377
 
378
378
  const config = await containerManager.getConfig(containerNameFinal)
379
379
  if (config) {
380
- await dbEngine.start(config)
380
+ try {
381
+ await dbEngine.start(config)
382
+ } catch (error) {
383
+ startSpinner.fail(`${dbEngine.displayName} failed to start`)
384
+ const e = error as Error
385
+ console.log()
386
+ console.log(uiError(e.message))
387
+ console.log()
388
+ // Clean up the container that was created but failed to start
389
+ try {
390
+ await containerManager.delete(containerNameFinal, { force: true })
391
+ } catch {
392
+ // Ignore cleanup errors
393
+ }
394
+ await pressEnterToContinue()
395
+ return
396
+ }
381
397
  await containerManager.updateConfig(containerNameFinal, {
382
398
  status: 'running',
383
399
  })
@@ -34,6 +34,7 @@ import {
34
34
  type InstalledInfluxDBEngine,
35
35
  } from '../../helpers'
36
36
 
37
+ import { isV1 } from '../../../engines/ferretdb/version-maps'
37
38
  import { type MenuChoice } from './shared'
38
39
 
39
40
  export async function handleEngines(): Promise<void> {
@@ -285,50 +286,68 @@ async function handleDeleteEngine(
285
286
  try {
286
287
  await rm(enginePath, { recursive: true, force: true })
287
288
 
288
- // FerretDB is a composite engine - also clean up postgresql-documentdb backend
289
- // But only if no other FerretDB installations share it
289
+ // FerretDB is a composite engine - handle backend cleanup based on version
290
+ let backendStatus = ''
290
291
  if (engineName === 'ferretdb') {
291
292
  // enginePath is like: ~/.spindb/bin/ferretdb-2.7.0-darwin-arm64
292
- // We need to find: ~/.spindb/bin/postgresql-documentdb-*-darwin-arm64
293
293
  const binDir = dirname(enginePath)
294
294
  const ferretDirName = basename(enginePath)
295
- // Extract platform-arch from ferretdb directory name (e.g., "darwin-arm64")
295
+ // Extract version from directory name (e.g., "2.7.0" from "ferretdb-2.7.0-darwin-arm64")
296
296
  const parts = ferretDirName.split('-')
297
297
  const platformArch = parts.slice(-2).join('-') // "darwin-arm64"
298
298
 
299
- const entries = await readdir(binDir, { withFileTypes: true })
300
-
301
- // Check if other FerretDB installations exist for the same platform
302
- // If so, don't delete the shared postgresql-documentdb backend
303
- const otherFerretInstalls = entries.filter(
304
- (entry) =>
305
- entry.isDirectory() &&
306
- entry.name.startsWith('ferretdb-') &&
307
- entry.name.endsWith(platformArch) &&
308
- entry.name !== ferretDirName,
299
+ // Extract version: remove "ferretdb-" prefix and "-platform-arch" suffix
300
+ const versionPart = ferretDirName.slice(
301
+ 'ferretdb-'.length,
302
+ ferretDirName.length - `-${platformArch}`.length,
309
303
  )
310
304
 
311
- if (otherFerretInstalls.length > 0) {
312
- // Other FerretDB versions exist - skip documentdb deletion
313
- spinner.text = `Skipping postgresql-documentdb (shared by ${otherFerretInstalls.length} other FerretDB install(s))`
305
+ if (isV1(versionPart)) {
306
+ // v1: Don't delete shared PostgreSQL binaries (used by standalone PG containers)
307
+ backendStatus =
308
+ ' (PostgreSQL backend kept β€” shared with standalone containers)'
314
309
  } else {
315
- // No other FerretDB installs - safe to delete documentdb backend
316
- const documentdbPattern = `postgresql-documentdb-`
317
- for (const entry of entries) {
318
- if (
319
- entry.isDirectory() &&
320
- entry.name.startsWith(documentdbPattern) &&
321
- entry.name.endsWith(platformArch)
322
- ) {
323
- const documentdbPath = join(binDir, entry.name)
324
- spinner.text = `Deleting postgresql-documentdb backend...`
325
- await rm(documentdbPath, { recursive: true, force: true })
310
+ // v2: Clean up postgresql-documentdb backend if no other v2 FerretDB installs share it
311
+ const entries = await readdir(binDir, { withFileTypes: true })
312
+
313
+ // Check if other v2 FerretDB installations exist for the same platform
314
+ const otherV2Installs = entries.filter((entry) => {
315
+ if (!entry.isDirectory()) return false
316
+ if (!entry.name.startsWith('ferretdb-')) return false
317
+ if (!entry.name.endsWith(platformArch)) return false
318
+ if (entry.name === ferretDirName) return false
319
+ const otherVersion = entry.name.slice(
320
+ 'ferretdb-'.length,
321
+ entry.name.length - `-${platformArch}`.length,
322
+ )
323
+ return !isV1(otherVersion)
324
+ })
325
+
326
+ if (otherV2Installs.length > 0) {
327
+ backendStatus = ` (postgresql-documentdb kept β€” shared by ${otherV2Installs.length} other v2 install(s))`
328
+ } else {
329
+ const documentdbPattern = `postgresql-documentdb-`
330
+ let cleaned = false
331
+ for (const entry of entries) {
332
+ if (
333
+ entry.isDirectory() &&
334
+ entry.name.startsWith(documentdbPattern) &&
335
+ entry.name.endsWith(platformArch)
336
+ ) {
337
+ const documentdbPath = join(binDir, entry.name)
338
+ spinner.text = `Deleting postgresql-documentdb backend...`
339
+ await rm(documentdbPath, { recursive: true, force: true })
340
+ cleaned = true
341
+ }
342
+ }
343
+ if (cleaned) {
344
+ backendStatus = ' (postgresql-documentdb backend also deleted)'
326
345
  }
327
346
  }
328
347
  }
329
348
  }
330
349
 
331
- spinner.succeed(`Deleted ${engineName} ${engineVersion}`)
350
+ spinner.succeed(`Deleted ${engineName} ${engineVersion}${backendStatus}`)
332
351
  } catch (error) {
333
352
  const e = error as Error
334
353
  spinner.fail(`Failed to delete: ${e.message}`)
package/cli/ui/theme.ts CHANGED
@@ -117,9 +117,12 @@ export function connectionBox(
117
117
  }
118
118
 
119
119
  export function formatBytes(bytes: number): string {
120
- if (bytes === 0) return '0 B'
120
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
121
121
  const units = ['B', 'KB', 'MB', 'GB', 'TB']
122
- const i = Math.floor(Math.log(bytes) / Math.log(1024))
122
+ const i = Math.max(
123
+ 0,
124
+ Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1),
125
+ )
123
126
  const value = bytes / Math.pow(1024, i)
124
127
  return `${value.toFixed(1)} ${units[i]}`
125
128
  }
@@ -19,6 +19,8 @@ export type EngineConfig = {
19
19
  clientTools: string[]
20
20
  licensing?: string | string[]
21
21
  notes?: string
22
+ platforms?: string[]
23
+ versionPlatforms?: Record<string, string[]>
22
24
  }
23
25
 
24
26
  export type EnginesJson = {
@@ -92,3 +94,57 @@ export function getAllEngines(): Engine[] {
92
94
  export function clearEnginesCache(): void {
93
95
  cachedEngines = null
94
96
  }
97
+
98
+ /**
99
+ * Filter engines data to only include engines and versions supported on the given platform.
100
+ *
101
+ * - If an engine has `platforms` and the platformKey isn't listed, the engine is removed.
102
+ * - If an engine has `versionPlatforms`, versions whose entry excludes the platformKey are removed.
103
+ * Versions with no entry in `versionPlatforms` are kept (assumed all-platform).
104
+ * - If filtering removes all versions, the engine is removed.
105
+ * - If the defaultVersion is removed, it's set to the first remaining version.
106
+ */
107
+ export function filterEnginesByPlatform(
108
+ enginesData: EnginesJson,
109
+ platformKey: string,
110
+ ): EnginesJson {
111
+ const filtered: Record<string, EngineConfig> = {}
112
+
113
+ for (const [name, config] of Object.entries(enginesData.engines)) {
114
+ // Engine-level platform check
115
+ if (config.platforms && !config.platforms.includes(platformKey)) {
116
+ continue
117
+ }
118
+
119
+ // Version-level platform check
120
+ if (config.versionPlatforms) {
121
+ const filteredVersions = config.supportedVersions.filter((version) => {
122
+ const platforms = config.versionPlatforms![version]
123
+ // If no entry for this version, it's available on all platforms
124
+ if (!platforms) return true
125
+ return platforms.includes(platformKey)
126
+ })
127
+
128
+ if (filteredVersions.length === 0) {
129
+ continue
130
+ }
131
+
132
+ const defaultVersion = filteredVersions.includes(config.defaultVersion)
133
+ ? config.defaultVersion
134
+ : filteredVersions[0]
135
+
136
+ filtered[name] = {
137
+ ...config,
138
+ supportedVersions: filteredVersions,
139
+ defaultVersion,
140
+ }
141
+ } else {
142
+ filtered[name] = config
143
+ }
144
+ }
145
+
146
+ return {
147
+ ...enginesData,
148
+ engines: filtered as Record<Engine, EngineConfig>,
149
+ }
150
+ }
@@ -144,7 +144,8 @@
144
144
  "superuser": "default",
145
145
  "clientTools": ["clickhouse"],
146
146
  "licensing": "Apache-2.0",
147
- "notes": "Column-oriented OLAP database. Uses YY.MM.X.build versioning. Native port 9000, HTTP port 8123. WSL only on Windows."
147
+ "notes": "Column-oriented OLAP database. Uses YY.MM.X.build versioning. Native port 9000, HTTP port 8123. WSL only on Windows.",
148
+ "platforms": ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64"]
148
149
  },
149
150
  "qdrant": {
150
151
  "displayName": "Qdrant",
@@ -185,7 +186,7 @@
185
186
  "icon": "πŸ¦”",
186
187
  "status": "integrated",
187
188
  "binarySource": "hostdb",
188
- "supportedVersions": ["2.7.0"],
189
+ "supportedVersions": ["1.24.2", "2.7.0"],
189
190
  "defaultVersion": "2.7.0",
190
191
  "defaultPort": 27017,
191
192
  "runtime": "server",
@@ -195,7 +196,17 @@
195
196
  "superuser": null,
196
197
  "clientTools": ["ferretdb", "mongosh", "mongodump", "mongorestore"],
197
198
  "licensing": "Apache-2.0",
198
- "notes": "MongoDB-compatible proxy using PostgreSQL as backend. Requires postgresql-documentdb."
199
+ "notes": "MongoDB-compatible proxy. v2 uses postgresql-documentdb (macOS/Linux). v1 uses plain PostgreSQL (all platforms incl. Windows).",
200
+ "versionPlatforms": {
201
+ "1.24.2": [
202
+ "darwin-arm64",
203
+ "darwin-x64",
204
+ "linux-arm64",
205
+ "linux-x64",
206
+ "win32-x64"
207
+ ],
208
+ "2.7.0": ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64"]
209
+ }
199
210
  },
200
211
  "couchdb": {
201
212
  "displayName": "CouchDB",
@@ -114,6 +114,19 @@
114
114
  "notes": {
115
115
  "type": "string",
116
116
  "description": "Additional notes about the engine"
117
+ },
118
+ "platforms": {
119
+ "type": "array",
120
+ "items": { "type": "string" },
121
+ "description": "Supported platform-arch combos (e.g. 'darwin-arm64'). If omitted, all platforms are supported."
122
+ },
123
+ "versionPlatforms": {
124
+ "type": "object",
125
+ "additionalProperties": {
126
+ "type": "array",
127
+ "items": { "type": "string" }
128
+ },
129
+ "description": "Per-version platform overrides. Keys are version strings, values are platform-arch arrays."
117
130
  }
118
131
  },
119
132
  "additionalProperties": false
@@ -17,6 +17,7 @@ import { paths } from '../config/paths'
17
17
  import { spawnAsync } from './spawn-utils'
18
18
  import { moveEntry } from './fs-error-utils'
19
19
  import { logDebug } from './error-handler'
20
+ import { fetchWithRegistryFallback } from './hostdb-client'
20
21
  import {
21
22
  type Engine,
22
23
  Platform,
@@ -186,7 +187,9 @@ export abstract class BaseBinaryManager {
186
187
 
187
188
  let response: Response
188
189
  try {
189
- response = await fetch(url, { signal: controller.signal })
190
+ response = await fetchWithRegistryFallback(url, {
191
+ signal: controller.signal,
192
+ })
190
193
  } catch (error) {
191
194
  const err = error as Error
192
195
  if (err.name === 'AbortError') {
@@ -202,7 +205,7 @@ export abstract class BaseBinaryManager {
202
205
  throw new Error(
203
206
  `${this.config.displayName} ${fullVersion} binaries not found (404). ` +
204
207
  `This version may have been removed from hostdb. ` +
205
- `Try a different version or check https://github.com/robertjbass/hostdb/releases`,
208
+ `Try a different version or check https://registry.layerbase.host`,
206
209
  )
207
210
  }
208
211
  throw new Error(
@@ -451,6 +454,7 @@ export abstract class BaseBinaryManager {
451
454
  try {
452
455
  const { stdout, stderr } = await spawnAsync(serverPath, ['--version'], {
453
456
  timeout: this.verifyTimeoutMs,
457
+ cwd: binPath,
454
458
  })
455
459
  // Log stderr if present (may contain warnings)
456
460
  if (stderr && stderr.trim()) {
@@ -25,6 +25,7 @@ import { paths } from '../config/paths'
25
25
  import { spawnAsync, extractWindowsArchive } from './spawn-utils'
26
26
  import { isRenameFallbackError } from './fs-error-utils'
27
27
  import { logDebug } from './error-handler'
28
+ import { fetchWithRegistryFallback } from './hostdb-client'
28
29
  import {
29
30
  type Engine,
30
31
  Platform,
@@ -203,14 +204,16 @@ export abstract class BaseDocumentBinaryManager {
203
204
  let response: Response
204
205
  let fileStream: ReturnType<typeof createWriteStream> | null = null
205
206
  try {
206
- response = await fetch(url, { signal: controller.signal })
207
+ response = await fetchWithRegistryFallback(url, {
208
+ signal: controller.signal,
209
+ })
207
210
 
208
211
  if (!response.ok) {
209
212
  if (response.status === 404) {
210
213
  throw new Error(
211
214
  `${this.config.displayName} ${fullVersion} binaries not found (404). ` +
212
215
  `This version may have been removed from hostdb. ` +
213
- `Try a different version or check https://github.com/robertjbass/hostdb/releases`,
216
+ `Try a different version or check https://registry.layerbase.host`,
214
217
  )
215
218
  }
216
219
  throw new Error(
@@ -23,6 +23,7 @@ import { paths } from '../config/paths'
23
23
  import { spawnAsync } from './spawn-utils'
24
24
  import { moveEntry } from './fs-error-utils'
25
25
  import { compareVersions } from './version-utils'
26
+ import { fetchWithRegistryFallback } from './hostdb-client'
26
27
  import {
27
28
  type Engine,
28
29
  Platform,
@@ -201,7 +202,9 @@ export abstract class BaseEmbeddedBinaryManager {
201
202
 
202
203
  let response: Response
203
204
  try {
204
- response = await fetch(url, { signal: controller.signal })
205
+ response = await fetchWithRegistryFallback(url, {
206
+ signal: controller.signal,
207
+ })
205
208
  } catch (error) {
206
209
  const err = error as Error
207
210
  if (err.name === 'AbortError') {
@@ -220,7 +223,7 @@ export abstract class BaseEmbeddedBinaryManager {
220
223
  throw new Error(
221
224
  `${this.config.displayName} ${fullVersion} binaries not found (404). ` +
222
225
  `This version may have been removed from hostdb. ` +
223
- `Try a different version or check https://github.com/robertjbass/hostdb/releases`,
226
+ `Try a different version or check https://registry.layerbase.host`,
224
227
  )
225
228
  }
226
229
  throw new Error(
@@ -23,6 +23,7 @@ import { pipeline } from 'stream/promises'
23
23
  import { paths } from '../config/paths'
24
24
  import { spawnAsync, extractWindowsArchive } from './spawn-utils'
25
25
  import { isRenameFallbackError } from './fs-error-utils'
26
+ import { fetchWithRegistryFallback } from './hostdb-client'
26
27
  import {
27
28
  type Engine,
28
29
  Platform,
@@ -205,7 +206,9 @@ export abstract class BaseServerBinaryManager {
205
206
 
206
207
  let response: Response
207
208
  try {
208
- response = await fetch(url, { signal: controller.signal })
209
+ response = await fetchWithRegistryFallback(url, {
210
+ signal: controller.signal,
211
+ })
209
212
  } catch (error) {
210
213
  const err = error as Error
211
214
  if (err.name === 'AbortError') {
@@ -224,7 +227,7 @@ export abstract class BaseServerBinaryManager {
224
227
  throw new Error(
225
228
  `${this.config.displayName} ${fullVersion} binaries not found (404). ` +
226
229
  `This version may have been removed from hostdb. ` +
227
- `Try a different version or check https://github.com/robertjbass/hostdb/releases`,
230
+ `Try a different version or check https://registry.layerbase.host`,
228
231
  )
229
232
  }
230
233
  throw new Error(