spindb 0.37.2 → 0.38.0
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/dist/cli/bin.js +9 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/attach.js +102 -0
- package/dist/cli/commands/attach.js.map +1 -0
- package/dist/cli/commands/backup.js +197 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/backups.js +190 -0
- package/dist/cli/commands/backups.js.map +1 -0
- package/dist/cli/commands/clone.js +119 -0
- package/dist/cli/commands/clone.js.map +1 -0
- package/dist/cli/commands/config.js +276 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/connect.js +559 -0
- package/dist/cli/commands/connect.js.map +1 -0
- package/dist/cli/commands/create.js +952 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/databases.js +485 -0
- package/dist/cli/commands/databases.js.map +1 -0
- package/dist/cli/commands/delete.js +106 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/deps.js +238 -0
- package/dist/cli/commands/deps.js.map +1 -0
- package/dist/cli/commands/detach.js +81 -0
- package/dist/cli/commands/detach.js.map +1 -0
- package/dist/cli/commands/doctor.js +567 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/duckdb.js +207 -0
- package/dist/cli/commands/duckdb.js.map +1 -0
- package/dist/cli/commands/edit.js +524 -0
- package/dist/cli/commands/edit.js.map +1 -0
- package/dist/cli/commands/engines.js +1414 -0
- package/dist/cli/commands/engines.js.map +1 -0
- package/dist/cli/commands/export.js +383 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/info.js +270 -0
- package/dist/cli/commands/info.js.map +1 -0
- package/dist/cli/commands/list.js +215 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/logs.js +81 -0
- package/dist/cli/commands/logs.js.map +1 -0
- package/dist/cli/commands/menu/backup-handlers.js +1202 -0
- package/dist/cli/commands/menu/backup-handlers.js.map +1 -0
- package/dist/cli/commands/menu/container-handlers.js +1788 -0
- package/dist/cli/commands/menu/container-handlers.js.map +1 -0
- package/dist/cli/commands/menu/engine-handlers.js +235 -0
- package/dist/cli/commands/menu/engine-handlers.js.map +1 -0
- package/dist/cli/commands/menu/index.js +266 -0
- package/dist/cli/commands/menu/index.js.map +1 -0
- package/dist/cli/commands/menu/settings-handlers.js +320 -0
- package/dist/cli/commands/menu/settings-handlers.js.map +1 -0
- package/dist/cli/commands/menu/shared.js +13 -0
- package/dist/cli/commands/menu/shared.js.map +1 -0
- package/dist/cli/commands/menu/shell-handlers.js +1573 -0
- package/dist/cli/commands/menu/shell-handlers.js.map +1 -0
- package/dist/cli/commands/menu/sql-handlers.js +185 -0
- package/dist/cli/commands/menu/sql-handlers.js.map +1 -0
- package/dist/cli/commands/menu/update-handlers.js +322 -0
- package/dist/cli/commands/menu/update-handlers.js.map +1 -0
- package/dist/cli/commands/menu/validators.js +9 -0
- package/dist/cli/commands/menu/validators.js.map +1 -0
- package/dist/cli/commands/ports.js +166 -0
- package/dist/cli/commands/ports.js.map +1 -0
- package/dist/cli/commands/pull.js +166 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/query.js +180 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/restore.js +428 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/run.js +115 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/self-update.js +99 -0
- package/dist/cli/commands/self-update.js.map +1 -0
- package/dist/cli/commands/sqlite.js +207 -0
- package/dist/cli/commands/sqlite.js.map +1 -0
- package/dist/cli/commands/start.js +196 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/stop.js +182 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/commands/url.js +88 -0
- package/dist/cli/commands/url.js.map +1 -0
- package/dist/cli/commands/users.js +189 -0
- package/dist/cli/commands/users.js.map +1 -0
- package/dist/cli/commands/version.js +52 -0
- package/dist/cli/commands/version.js.map +1 -0
- package/dist/cli/commands/which.js +258 -0
- package/dist/cli/commands/which.js.map +1 -0
- package/dist/cli/constants.js +212 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/helpers.js +1120 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/index.js +146 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/prompts.js +1002 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/ui/spinner.js +74 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/cli/ui/theme.js +99 -0
- package/dist/cli/ui/theme.js.map +1 -0
- package/dist/cli/utils/file-follower.js +79 -0
- package/dist/cli/utils/file-follower.js.map +1 -0
- package/dist/config/backup-formats.js +363 -0
- package/dist/config/backup-formats.js.map +1 -0
- package/dist/config/defaults.js +25 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/engine-defaults.js +303 -0
- package/dist/config/engine-defaults.js.map +1 -0
- package/dist/config/engines-registry.js +103 -0
- package/dist/config/engines-registry.js.map +1 -0
- package/dist/config/os-dependencies.js +767 -0
- package/dist/config/os-dependencies.js.map +1 -0
- package/dist/config/paths.js +156 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/version.js +3 -0
- package/dist/config/version.js.map +1 -0
- package/dist/core/backup-restore.js +219 -0
- package/dist/core/backup-restore.js.map +1 -0
- package/dist/core/base-binary-manager.js +403 -0
- package/dist/core/base-binary-manager.js.map +1 -0
- package/dist/core/base-document-binary-manager.js +364 -0
- package/dist/core/base-document-binary-manager.js.map +1 -0
- package/dist/core/base-embedded-binary-manager.js +364 -0
- package/dist/core/base-embedded-binary-manager.js.map +1 -0
- package/dist/core/base-server-binary-manager.js +368 -0
- package/dist/core/base-server-binary-manager.js.map +1 -0
- package/dist/core/config-manager.js +495 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/container-manager.js +609 -0
- package/dist/core/container-manager.js.map +1 -0
- package/dist/core/credential-generator.js +67 -0
- package/dist/core/credential-generator.js.map +1 -0
- package/dist/core/credential-manager.js +211 -0
- package/dist/core/credential-manager.js.map +1 -0
- package/dist/core/dblab-utils.js +105 -0
- package/dist/core/dblab-utils.js.map +1 -0
- package/dist/core/dependency-manager.js +359 -0
- package/dist/core/dependency-manager.js.map +1 -0
- package/dist/core/docker-exporter.js +1077 -0
- package/dist/core/docker-exporter.js.map +1 -0
- package/dist/core/error-handler.js +295 -0
- package/dist/core/error-handler.js.map +1 -0
- package/dist/core/fs-error-utils.js +74 -0
- package/dist/core/fs-error-utils.js.map +1 -0
- package/dist/core/homebrew-version-manager.js +280 -0
- package/dist/core/homebrew-version-manager.js.map +1 -0
- package/dist/core/hostdb-client.js +252 -0
- package/dist/core/hostdb-client.js.map +1 -0
- package/dist/core/hostdb-metadata.js +243 -0
- package/dist/core/hostdb-metadata.js.map +1 -0
- package/dist/core/hostdb-releases-factory.js +161 -0
- package/dist/core/hostdb-releases-factory.js.map +1 -0
- package/dist/core/library-env.js +88 -0
- package/dist/core/library-env.js.map +1 -0
- package/dist/core/pgweb-utils.js +53 -0
- package/dist/core/pgweb-utils.js.map +1 -0
- package/dist/core/platform-service.js +632 -0
- package/dist/core/platform-service.js.map +1 -0
- package/dist/core/port-manager.js +136 -0
- package/dist/core/port-manager.js.map +1 -0
- package/dist/core/process-manager.js +445 -0
- package/dist/core/process-manager.js.map +1 -0
- package/dist/core/pull-manager.js +418 -0
- package/dist/core/pull-manager.js.map +1 -0
- package/dist/core/query-parser.js +449 -0
- package/dist/core/query-parser.js.map +1 -0
- package/dist/core/spawn-utils.js +90 -0
- package/dist/core/spawn-utils.js.map +1 -0
- package/dist/core/start-with-retry.js +90 -0
- package/dist/core/start-with-retry.js.map +1 -0
- package/dist/core/test-cleanup.js +85 -0
- package/dist/core/test-cleanup.js.map +1 -0
- package/dist/core/tls-generator.js +84 -0
- package/dist/core/tls-generator.js.map +1 -0
- package/dist/core/transaction-manager.js +139 -0
- package/dist/core/transaction-manager.js.map +1 -0
- package/dist/core/update-manager.js +241 -0
- package/dist/core/update-manager.js.map +1 -0
- package/dist/core/version-migration.js +260 -0
- package/dist/core/version-migration.js.map +1 -0
- package/dist/core/version-utils.js +91 -0
- package/dist/core/version-utils.js.map +1 -0
- package/dist/engines/base-engine.js +179 -0
- package/dist/engines/base-engine.js.map +1 -0
- package/dist/engines/clickhouse/backup.js +289 -0
- package/dist/engines/clickhouse/backup.js.map +1 -0
- package/dist/engines/clickhouse/binary-manager.js +145 -0
- package/dist/engines/clickhouse/binary-manager.js.map +1 -0
- package/dist/engines/clickhouse/binary-urls.js +100 -0
- package/dist/engines/clickhouse/binary-urls.js.map +1 -0
- package/dist/engines/clickhouse/cli-utils.js +143 -0
- package/dist/engines/clickhouse/cli-utils.js.map +1 -0
- package/dist/engines/clickhouse/hostdb-releases.js +24 -0
- package/dist/engines/clickhouse/hostdb-releases.js.map +1 -0
- package/dist/engines/clickhouse/index.js +1077 -0
- package/dist/engines/clickhouse/index.js.map +1 -0
- package/dist/engines/clickhouse/restore.js +335 -0
- package/dist/engines/clickhouse/restore.js.map +1 -0
- package/dist/engines/clickhouse/version-maps.js +83 -0
- package/dist/engines/clickhouse/version-maps.js.map +1 -0
- package/dist/engines/clickhouse/version-validator.js +133 -0
- package/dist/engines/clickhouse/version-validator.js.map +1 -0
- package/dist/engines/cockroachdb/backup.js +261 -0
- package/dist/engines/cockroachdb/backup.js.map +1 -0
- package/dist/engines/cockroachdb/binary-manager.js +33 -0
- package/dist/engines/cockroachdb/binary-manager.js.map +1 -0
- package/dist/engines/cockroachdb/binary-urls.js +33 -0
- package/dist/engines/cockroachdb/binary-urls.js.map +1 -0
- package/dist/engines/cockroachdb/cli-utils.js +338 -0
- package/dist/engines/cockroachdb/cli-utils.js.map +1 -0
- package/dist/engines/cockroachdb/hostdb-releases.js +21 -0
- package/dist/engines/cockroachdb/hostdb-releases.js.map +1 -0
- package/dist/engines/cockroachdb/index.js +1016 -0
- package/dist/engines/cockroachdb/index.js.map +1 -0
- package/dist/engines/cockroachdb/restore.js +323 -0
- package/dist/engines/cockroachdb/restore.js.map +1 -0
- package/dist/engines/cockroachdb/version-maps.js +37 -0
- package/dist/engines/cockroachdb/version-maps.js.map +1 -0
- package/dist/engines/couchdb/api-client.js +64 -0
- package/dist/engines/couchdb/api-client.js.map +1 -0
- package/dist/engines/couchdb/backup.js +90 -0
- package/dist/engines/couchdb/backup.js.map +1 -0
- package/dist/engines/couchdb/binary-manager.js +62 -0
- package/dist/engines/couchdb/binary-manager.js.map +1 -0
- package/dist/engines/couchdb/binary-urls.js +92 -0
- package/dist/engines/couchdb/binary-urls.js.map +1 -0
- package/dist/engines/couchdb/hostdb-releases.js +21 -0
- package/dist/engines/couchdb/hostdb-releases.js.map +1 -0
- package/dist/engines/couchdb/index.js +1043 -0
- package/dist/engines/couchdb/index.js.map +1 -0
- package/dist/engines/couchdb/restore.js +198 -0
- package/dist/engines/couchdb/restore.js.map +1 -0
- package/dist/engines/couchdb/version-maps.js +67 -0
- package/dist/engines/couchdb/version-maps.js.map +1 -0
- package/dist/engines/couchdb/version-validator.js +88 -0
- package/dist/engines/couchdb/version-validator.js.map +1 -0
- package/dist/engines/duckdb/binary-manager.js +33 -0
- package/dist/engines/duckdb/binary-manager.js.map +1 -0
- package/{engines/duckdb/binary-urls.ts → dist/engines/duckdb/binary-urls.js} +11 -16
- package/dist/engines/duckdb/binary-urls.js.map +1 -0
- package/dist/engines/duckdb/hostdb-releases.js +21 -0
- package/dist/engines/duckdb/hostdb-releases.js.map +1 -0
- package/dist/engines/duckdb/index.js +594 -0
- package/dist/engines/duckdb/index.js.map +1 -0
- package/dist/engines/duckdb/registry.js +265 -0
- package/dist/engines/duckdb/registry.js.map +1 -0
- package/dist/engines/duckdb/scanner.js +12 -0
- package/dist/engines/duckdb/scanner.js.map +1 -0
- package/dist/engines/duckdb/version-maps.js +67 -0
- package/dist/engines/duckdb/version-maps.js.map +1 -0
- package/dist/engines/duckdb/version-validator.js +62 -0
- package/dist/engines/duckdb/version-validator.js.map +1 -0
- package/dist/engines/ferretdb/backup.js +170 -0
- package/dist/engines/ferretdb/backup.js.map +1 -0
- package/dist/engines/ferretdb/binary-manager.js +765 -0
- package/dist/engines/ferretdb/binary-manager.js.map +1 -0
- package/dist/engines/ferretdb/binary-urls.js +135 -0
- package/dist/engines/ferretdb/binary-urls.js.map +1 -0
- package/dist/engines/ferretdb/index.js +1517 -0
- package/dist/engines/ferretdb/index.js.map +1 -0
- package/dist/engines/ferretdb/restore.js +310 -0
- package/dist/engines/ferretdb/restore.js.map +1 -0
- package/{engines/ferretdb/version-maps.ts → dist/engines/ferretdb/version-maps.js} +62 -79
- package/dist/engines/ferretdb/version-maps.js.map +1 -0
- package/dist/engines/file-based-utils.js +184 -0
- package/dist/engines/file-based-utils.js.map +1 -0
- package/dist/engines/index.js +124 -0
- package/dist/engines/index.js.map +1 -0
- package/dist/engines/influxdb/api-client.js +54 -0
- package/dist/engines/influxdb/api-client.js.map +1 -0
- package/dist/engines/influxdb/backup.js +119 -0
- package/dist/engines/influxdb/backup.js.map +1 -0
- package/dist/engines/influxdb/binary-manager.js +87 -0
- package/dist/engines/influxdb/binary-manager.js.map +1 -0
- package/dist/engines/influxdb/binary-urls.js +56 -0
- package/dist/engines/influxdb/binary-urls.js.map +1 -0
- package/dist/engines/influxdb/hostdb-releases.js +21 -0
- package/dist/engines/influxdb/hostdb-releases.js.map +1 -0
- package/dist/engines/influxdb/index.js +962 -0
- package/dist/engines/influxdb/index.js.map +1 -0
- package/dist/engines/influxdb/restore.js +329 -0
- package/dist/engines/influxdb/restore.js.map +1 -0
- package/dist/engines/influxdb/version-maps.js +64 -0
- package/dist/engines/influxdb/version-maps.js.map +1 -0
- package/dist/engines/influxdb/version-validator.js +109 -0
- package/dist/engines/influxdb/version-validator.js.map +1 -0
- package/dist/engines/mariadb/backup.js +178 -0
- package/dist/engines/mariadb/backup.js.map +1 -0
- package/dist/engines/mariadb/binary-manager.js +33 -0
- package/dist/engines/mariadb/binary-manager.js.map +1 -0
- package/{engines/mariadb/binary-urls.ts → dist/engines/mariadb/binary-urls.js} +38 -55
- package/dist/engines/mariadb/binary-urls.js.map +1 -0
- package/dist/engines/mariadb/hostdb-releases.js +21 -0
- package/dist/engines/mariadb/hostdb-releases.js.map +1 -0
- package/dist/engines/mariadb/index.js +1011 -0
- package/dist/engines/mariadb/index.js.map +1 -0
- package/dist/engines/mariadb/restore.js +322 -0
- package/dist/engines/mariadb/restore.js.map +1 -0
- package/dist/engines/mariadb/version-maps.js +63 -0
- package/dist/engines/mariadb/version-maps.js.map +1 -0
- package/dist/engines/mariadb/version-validator.js +143 -0
- package/dist/engines/mariadb/version-validator.js.map +1 -0
- package/dist/engines/meilisearch/api-client.js +50 -0
- package/dist/engines/meilisearch/api-client.js.map +1 -0
- package/dist/engines/meilisearch/backup.js +167 -0
- package/dist/engines/meilisearch/backup.js.map +1 -0
- package/dist/engines/meilisearch/binary-manager.js +31 -0
- package/dist/engines/meilisearch/binary-manager.js.map +1 -0
- package/dist/engines/meilisearch/binary-urls.js +56 -0
- package/dist/engines/meilisearch/binary-urls.js.map +1 -0
- package/dist/engines/meilisearch/hostdb-releases.js +21 -0
- package/dist/engines/meilisearch/hostdb-releases.js.map +1 -0
- package/dist/engines/meilisearch/index.js +992 -0
- package/dist/engines/meilisearch/index.js.map +1 -0
- package/dist/engines/meilisearch/restore.js +167 -0
- package/dist/engines/meilisearch/restore.js.map +1 -0
- package/dist/engines/meilisearch/version-maps.js +67 -0
- package/dist/engines/meilisearch/version-maps.js.map +1 -0
- package/dist/engines/meilisearch/version-validator.js +109 -0
- package/dist/engines/meilisearch/version-validator.js.map +1 -0
- package/dist/engines/mongodb/backup.js +109 -0
- package/dist/engines/mongodb/backup.js.map +1 -0
- package/dist/engines/mongodb/binary-manager.js +36 -0
- package/dist/engines/mongodb/binary-manager.js.map +1 -0
- package/dist/engines/mongodb/binary-urls.js +46 -0
- package/dist/engines/mongodb/binary-urls.js.map +1 -0
- package/dist/engines/mongodb/cli-utils.js +131 -0
- package/dist/engines/mongodb/cli-utils.js.map +1 -0
- package/dist/engines/mongodb/hostdb-releases.js +77 -0
- package/dist/engines/mongodb/hostdb-releases.js.map +1 -0
- package/dist/engines/mongodb/index.js +873 -0
- package/dist/engines/mongodb/index.js.map +1 -0
- package/dist/engines/mongodb/restore.js +276 -0
- package/dist/engines/mongodb/restore.js.map +1 -0
- package/dist/engines/mongodb/version-maps.js +79 -0
- package/dist/engines/mongodb/version-maps.js.map +1 -0
- package/dist/engines/mongodb/version-validator.js +133 -0
- package/dist/engines/mongodb/version-validator.js.map +1 -0
- package/dist/engines/mysql/backup.js +210 -0
- package/dist/engines/mysql/backup.js.map +1 -0
- package/dist/engines/mysql/binary-detection.js +325 -0
- package/dist/engines/mysql/binary-detection.js.map +1 -0
- package/dist/engines/mysql/binary-manager.js +30 -0
- package/dist/engines/mysql/binary-manager.js.map +1 -0
- package/dist/engines/mysql/binary-urls.js +87 -0
- package/dist/engines/mysql/binary-urls.js.map +1 -0
- package/{engines/mysql/hostdb-releases.ts → dist/engines/mysql/hostdb-releases.js} +20 -23
- package/dist/engines/mysql/hostdb-releases.js.map +1 -0
- package/dist/engines/mysql/index.js +1066 -0
- package/dist/engines/mysql/index.js.map +1 -0
- package/dist/engines/mysql/restore.js +361 -0
- package/dist/engines/mysql/restore.js.map +1 -0
- package/dist/engines/mysql/version-maps.js +79 -0
- package/dist/engines/mysql/version-maps.js.map +1 -0
- package/dist/engines/mysql/version-validator.js +266 -0
- package/dist/engines/mysql/version-validator.js.map +1 -0
- package/dist/engines/postgresql/backup.js +118 -0
- package/dist/engines/postgresql/backup.js.map +1 -0
- package/dist/engines/postgresql/binary-manager.js +85 -0
- package/dist/engines/postgresql/binary-manager.js.map +1 -0
- package/dist/engines/postgresql/binary-urls.js +80 -0
- package/dist/engines/postgresql/binary-urls.js.map +1 -0
- package/dist/engines/postgresql/hostdb-releases.js +21 -0
- package/dist/engines/postgresql/hostdb-releases.js.map +1 -0
- package/dist/engines/postgresql/index.js +852 -0
- package/dist/engines/postgresql/index.js.map +1 -0
- package/dist/engines/postgresql/remote-version.js +109 -0
- package/dist/engines/postgresql/remote-version.js.map +1 -0
- package/dist/engines/postgresql/restore.js +254 -0
- package/dist/engines/postgresql/restore.js.map +1 -0
- package/dist/engines/postgresql/version-maps.js +73 -0
- package/dist/engines/postgresql/version-maps.js.map +1 -0
- package/dist/engines/postgresql/version-validator.js +286 -0
- package/dist/engines/postgresql/version-validator.js.map +1 -0
- package/dist/engines/qdrant/api-client.js +50 -0
- package/dist/engines/qdrant/api-client.js.map +1 -0
- package/dist/engines/qdrant/backup.js +115 -0
- package/dist/engines/qdrant/backup.js.map +1 -0
- package/dist/engines/qdrant/binary-manager.js +31 -0
- package/dist/engines/qdrant/binary-manager.js.map +1 -0
- package/dist/engines/qdrant/binary-urls.js +92 -0
- package/dist/engines/qdrant/binary-urls.js.map +1 -0
- package/dist/engines/qdrant/cli-utils.js +39 -0
- package/dist/engines/qdrant/cli-utils.js.map +1 -0
- package/dist/engines/qdrant/hostdb-releases.js +21 -0
- package/dist/engines/qdrant/hostdb-releases.js.map +1 -0
- package/dist/engines/qdrant/index.js +1002 -0
- package/dist/engines/qdrant/index.js.map +1 -0
- package/dist/engines/qdrant/restore.js +154 -0
- package/dist/engines/qdrant/restore.js.map +1 -0
- package/dist/engines/qdrant/version-maps.js +67 -0
- package/dist/engines/qdrant/version-maps.js.map +1 -0
- package/dist/engines/qdrant/version-validator.js +109 -0
- package/dist/engines/qdrant/version-validator.js.map +1 -0
- package/dist/engines/questdb/backup.js +191 -0
- package/dist/engines/questdb/backup.js.map +1 -0
- package/dist/engines/questdb/binary-manager.js +247 -0
- package/dist/engines/questdb/binary-manager.js.map +1 -0
- package/dist/engines/questdb/binary-urls.js +27 -0
- package/dist/engines/questdb/binary-urls.js.map +1 -0
- package/dist/engines/questdb/hostdb-releases.js +21 -0
- package/dist/engines/questdb/hostdb-releases.js.map +1 -0
- package/dist/engines/questdb/index.js +814 -0
- package/dist/engines/questdb/index.js.map +1 -0
- package/dist/engines/questdb/restore.js +202 -0
- package/dist/engines/questdb/restore.js.map +1 -0
- package/dist/engines/questdb/version-maps.js +33 -0
- package/dist/engines/questdb/version-maps.js.map +1 -0
- package/dist/engines/questdb/version-validator.js +99 -0
- package/dist/engines/questdb/version-validator.js.map +1 -0
- package/dist/engines/redis/backup.js +292 -0
- package/dist/engines/redis/backup.js.map +1 -0
- package/dist/engines/redis/binary-manager.js +32 -0
- package/dist/engines/redis/binary-manager.js.map +1 -0
- package/dist/engines/redis/binary-urls.js +96 -0
- package/dist/engines/redis/binary-urls.js.map +1 -0
- package/dist/engines/redis/cli-utils.js +38 -0
- package/dist/engines/redis/cli-utils.js.map +1 -0
- package/dist/engines/redis/hostdb-releases.js +21 -0
- package/dist/engines/redis/hostdb-releases.js.map +1 -0
- package/dist/engines/redis/index.js +1263 -0
- package/dist/engines/redis/index.js.map +1 -0
- package/dist/engines/redis/restore.js +338 -0
- package/dist/engines/redis/restore.js.map +1 -0
- package/dist/engines/redis/version-maps.js +70 -0
- package/dist/engines/redis/version-maps.js.map +1 -0
- package/dist/engines/redis/version-validator.js +109 -0
- package/dist/engines/redis/version-validator.js.map +1 -0
- package/dist/engines/sqlite/binary-manager.js +39 -0
- package/dist/engines/sqlite/binary-manager.js.map +1 -0
- package/{engines/sqlite/binary-urls.ts → dist/engines/sqlite/binary-urls.js} +11 -16
- package/dist/engines/sqlite/binary-urls.js.map +1 -0
- package/dist/engines/sqlite/hostdb-releases.js +21 -0
- package/dist/engines/sqlite/hostdb-releases.js.map +1 -0
- package/dist/engines/sqlite/index.js +493 -0
- package/dist/engines/sqlite/index.js.map +1 -0
- package/dist/engines/sqlite/registry.js +163 -0
- package/dist/engines/sqlite/registry.js.map +1 -0
- package/dist/engines/sqlite/scanner.js +12 -0
- package/dist/engines/sqlite/scanner.js.map +1 -0
- package/dist/engines/sqlite/version-maps.js +57 -0
- package/dist/engines/sqlite/version-maps.js.map +1 -0
- package/dist/engines/surrealdb/backup.js +97 -0
- package/dist/engines/surrealdb/backup.js.map +1 -0
- package/dist/engines/surrealdb/binary-manager.js +33 -0
- package/dist/engines/surrealdb/binary-manager.js.map +1 -0
- package/dist/engines/surrealdb/binary-urls.js +33 -0
- package/dist/engines/surrealdb/binary-urls.js.map +1 -0
- package/dist/engines/surrealdb/cli-utils.js +147 -0
- package/dist/engines/surrealdb/cli-utils.js.map +1 -0
- package/dist/engines/surrealdb/hostdb-releases.js +21 -0
- package/dist/engines/surrealdb/hostdb-releases.js.map +1 -0
- package/dist/engines/surrealdb/index.js +1022 -0
- package/dist/engines/surrealdb/index.js.map +1 -0
- package/dist/engines/surrealdb/restore.js +224 -0
- package/dist/engines/surrealdb/restore.js.map +1 -0
- package/dist/engines/surrealdb/version-maps.js +36 -0
- package/dist/engines/surrealdb/version-maps.js.map +1 -0
- package/dist/engines/tigerbeetle/backup.js +36 -0
- package/dist/engines/tigerbeetle/backup.js.map +1 -0
- package/dist/engines/tigerbeetle/binary-manager.js +72 -0
- package/dist/engines/tigerbeetle/binary-manager.js.map +1 -0
- package/dist/engines/tigerbeetle/binary-urls.js +49 -0
- package/dist/engines/tigerbeetle/binary-urls.js.map +1 -0
- package/dist/engines/tigerbeetle/hostdb-releases.js +21 -0
- package/dist/engines/tigerbeetle/hostdb-releases.js.map +1 -0
- package/dist/engines/tigerbeetle/index.js +559 -0
- package/dist/engines/tigerbeetle/index.js.map +1 -0
- package/dist/engines/tigerbeetle/restore.js +91 -0
- package/dist/engines/tigerbeetle/restore.js.map +1 -0
- package/{engines/tigerbeetle/version-maps.ts → dist/engines/tigerbeetle/version-maps.js} +22 -31
- package/dist/engines/tigerbeetle/version-maps.js.map +1 -0
- package/dist/engines/tigerbeetle/version-validator.js +108 -0
- package/dist/engines/tigerbeetle/version-validator.js.map +1 -0
- package/dist/engines/typedb/backup.js +129 -0
- package/dist/engines/typedb/backup.js.map +1 -0
- package/dist/engines/typedb/binary-manager.js +151 -0
- package/dist/engines/typedb/binary-manager.js.map +1 -0
- package/dist/engines/typedb/binary-urls.js +33 -0
- package/dist/engines/typedb/binary-urls.js.map +1 -0
- package/dist/engines/typedb/cli-utils.js +163 -0
- package/dist/engines/typedb/cli-utils.js.map +1 -0
- package/dist/engines/typedb/hostdb-releases.js +21 -0
- package/dist/engines/typedb/hostdb-releases.js.map +1 -0
- package/dist/engines/typedb/index.js +1003 -0
- package/dist/engines/typedb/index.js.map +1 -0
- package/dist/engines/typedb/restore.js +279 -0
- package/dist/engines/typedb/restore.js.map +1 -0
- package/dist/engines/typedb/version-maps.js +40 -0
- package/dist/engines/typedb/version-maps.js.map +1 -0
- package/dist/engines/typedb/version-validator.js +103 -0
- package/dist/engines/typedb/version-validator.js.map +1 -0
- package/dist/engines/valkey/backup.js +292 -0
- package/dist/engines/valkey/backup.js.map +1 -0
- package/dist/engines/valkey/binary-manager.js +33 -0
- package/dist/engines/valkey/binary-manager.js.map +1 -0
- package/dist/engines/valkey/binary-urls.js +98 -0
- package/dist/engines/valkey/binary-urls.js.map +1 -0
- package/dist/engines/valkey/cli-utils.js +38 -0
- package/dist/engines/valkey/cli-utils.js.map +1 -0
- package/dist/engines/valkey/hostdb-releases.js +21 -0
- package/dist/engines/valkey/hostdb-releases.js.map +1 -0
- package/dist/engines/valkey/index.js +1257 -0
- package/dist/engines/valkey/index.js.map +1 -0
- package/dist/engines/valkey/restore.js +340 -0
- package/dist/engines/valkey/restore.js.map +1 -0
- package/dist/engines/valkey/version-maps.js +70 -0
- package/dist/engines/valkey/version-maps.js.map +1 -0
- package/dist/engines/valkey/version-validator.js +112 -0
- package/dist/engines/valkey/version-validator.js.map +1 -0
- package/dist/engines/weaviate/api-client.js +50 -0
- package/dist/engines/weaviate/api-client.js.map +1 -0
- package/dist/engines/weaviate/backup.js +95 -0
- package/dist/engines/weaviate/backup.js.map +1 -0
- package/dist/engines/weaviate/binary-manager.js +58 -0
- package/dist/engines/weaviate/binary-manager.js.map +1 -0
- package/dist/engines/weaviate/binary-urls.js +92 -0
- package/dist/engines/weaviate/binary-urls.js.map +1 -0
- package/dist/engines/weaviate/cli-utils.js +39 -0
- package/dist/engines/weaviate/cli-utils.js.map +1 -0
- package/dist/engines/weaviate/hostdb-releases.js +21 -0
- package/dist/engines/weaviate/hostdb-releases.js.map +1 -0
- package/dist/engines/weaviate/index.js +871 -0
- package/dist/engines/weaviate/index.js.map +1 -0
- package/dist/engines/weaviate/restore.js +185 -0
- package/dist/engines/weaviate/restore.js.map +1 -0
- package/dist/engines/weaviate/version-maps.js +67 -0
- package/dist/engines/weaviate/version-maps.js.map +1 -0
- package/dist/engines/weaviate/version-validator.js +109 -0
- package/dist/engines/weaviate/version-validator.js.map +1 -0
- package/dist/types/index.js +102 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +12 -9
- package/bin/cli.js +0 -68
- package/cli/bin.ts +0 -10
- package/cli/commands/attach.ts +0 -139
- package/cli/commands/backup.ts +0 -290
- package/cli/commands/backups.ts +0 -247
- package/cli/commands/clone.ts +0 -159
- package/cli/commands/config.ts +0 -367
- package/cli/commands/connect.ts +0 -684
- package/cli/commands/create.ts +0 -1201
- package/cli/commands/databases.ts +0 -630
- package/cli/commands/delete.ts +0 -133
- package/cli/commands/deps.ts +0 -342
- package/cli/commands/detach.ts +0 -107
- package/cli/commands/doctor.ts +0 -689
- package/cli/commands/duckdb.ts +0 -273
- package/cli/commands/edit.ts +0 -683
- package/cli/commands/engines.ts +0 -1914
- package/cli/commands/export.ts +0 -544
- package/cli/commands/info.ts +0 -340
- package/cli/commands/list.ts +0 -284
- package/cli/commands/logs.ts +0 -102
- package/cli/commands/menu/backup-handlers.ts +0 -1571
- package/cli/commands/menu/container-handlers.ts +0 -2288
- package/cli/commands/menu/engine-handlers.ts +0 -355
- package/cli/commands/menu/index.ts +0 -342
- package/cli/commands/menu/settings-handlers.ts +0 -365
- package/cli/commands/menu/shared.ts +0 -23
- package/cli/commands/menu/shell-handlers.ts +0 -1811
- package/cli/commands/menu/sql-handlers.ts +0 -231
- package/cli/commands/menu/update-handlers.ts +0 -378
- package/cli/commands/menu/validators.ts +0 -8
- package/cli/commands/ports.ts +0 -211
- package/cli/commands/pull.ts +0 -223
- package/cli/commands/query.ts +0 -241
- package/cli/commands/restore.ts +0 -587
- package/cli/commands/run.ts +0 -178
- package/cli/commands/self-update.ts +0 -121
- package/cli/commands/sqlite.ts +0 -273
- package/cli/commands/start.ts +0 -218
- package/cli/commands/stop.ts +0 -241
- package/cli/commands/url.ts +0 -104
- package/cli/commands/users.ts +0 -264
- package/cli/commands/version.ts +0 -55
- package/cli/commands/which.ts +0 -290
- package/cli/constants.ts +0 -233
- package/cli/helpers.ts +0 -1593
- package/cli/index.ts +0 -162
- package/cli/ui/prompts.ts +0 -1525
- package/cli/ui/spinner.ts +0 -88
- package/cli/ui/theme.ts +0 -128
- package/cli/utils/file-follower.ts +0 -93
- package/config/backup-formats.ts +0 -446
- package/config/defaults.ts +0 -56
- package/config/engine-defaults.ts +0 -336
- package/config/engines-registry.ts +0 -150
- package/config/engines.schema.json +0 -135
- package/config/os-dependencies.ts +0 -888
- package/config/paths.ts +0 -200
- package/core/backup-restore.ts +0 -330
- package/core/base-binary-manager.ts +0 -562
- package/core/base-document-binary-manager.ts +0 -523
- package/core/base-embedded-binary-manager.ts +0 -547
- package/core/base-server-binary-manager.ts +0 -523
- package/core/config-manager.ts +0 -652
- package/core/container-manager.ts +0 -787
- package/core/credential-generator.ts +0 -93
- package/core/credential-manager.ts +0 -259
- package/core/dblab-utils.ts +0 -113
- package/core/dependency-manager.ts +0 -512
- package/core/docker-exporter.ts +0 -1345
- package/core/error-handler.ts +0 -419
- package/core/fs-error-utils.ts +0 -82
- package/core/homebrew-version-manager.ts +0 -352
- package/core/hostdb-client.ts +0 -344
- package/core/hostdb-metadata.ts +0 -350
- package/core/hostdb-releases-factory.ts +0 -237
- package/core/library-env.ts +0 -118
- package/core/pgweb-utils.ts +0 -62
- package/core/platform-service.ts +0 -829
- package/core/port-manager.ts +0 -165
- package/core/process-manager.ts +0 -576
- package/core/pull-manager.ts +0 -511
- package/core/query-parser.ts +0 -514
- package/core/spawn-utils.ts +0 -122
- package/core/start-with-retry.ts +0 -130
- package/core/test-cleanup.ts +0 -108
- package/core/tls-generator.ts +0 -116
- package/core/transaction-manager.ts +0 -158
- package/core/update-manager.ts +0 -308
- package/core/version-migration.ts +0 -346
- package/core/version-utils.ts +0 -104
- package/engines/base-engine.ts +0 -340
- package/engines/clickhouse/README.md +0 -231
- package/engines/clickhouse/backup.ts +0 -398
- package/engines/clickhouse/binary-manager.ts +0 -201
- package/engines/clickhouse/binary-urls.ts +0 -125
- package/engines/clickhouse/cli-utils.ts +0 -176
- package/engines/clickhouse/hostdb-releases.ts +0 -30
- package/engines/clickhouse/index.ts +0 -1345
- package/engines/clickhouse/restore.ts +0 -466
- package/engines/clickhouse/version-maps.ts +0 -95
- package/engines/clickhouse/version-validator.ts +0 -154
- package/engines/cockroachdb/README.md +0 -170
- package/engines/cockroachdb/backup.ts +0 -376
- package/engines/cockroachdb/binary-manager.ts +0 -45
- package/engines/cockroachdb/binary-urls.ts +0 -40
- package/engines/cockroachdb/cli-utils.ts +0 -384
- package/engines/cockroachdb/hostdb-releases.ts +0 -26
- package/engines/cockroachdb/index.ts +0 -1276
- package/engines/cockroachdb/restore.ts +0 -455
- package/engines/cockroachdb/version-maps.ts +0 -42
- package/engines/couchdb/README.md +0 -257
- package/engines/couchdb/api-client.ts +0 -81
- package/engines/couchdb/backup.ts +0 -137
- package/engines/couchdb/binary-manager.ts +0 -86
- package/engines/couchdb/binary-urls.ts +0 -115
- package/engines/couchdb/hostdb-releases.ts +0 -23
- package/engines/couchdb/index.ts +0 -1429
- package/engines/couchdb/restore.ts +0 -290
- package/engines/couchdb/version-maps.ts +0 -78
- package/engines/couchdb/version-validator.ts +0 -111
- package/engines/duckdb/README.md +0 -154
- package/engines/duckdb/binary-manager.ts +0 -45
- package/engines/duckdb/hostdb-releases.ts +0 -23
- package/engines/duckdb/index.ts +0 -749
- package/engines/duckdb/registry.ts +0 -303
- package/engines/duckdb/scanner.ts +0 -22
- package/engines/duckdb/version-maps.ts +0 -78
- package/engines/duckdb/version-validator.ts +0 -78
- package/engines/ferretdb/README.md +0 -262
- package/engines/ferretdb/backup.ts +0 -173
- package/engines/ferretdb/binary-manager.ts +0 -1095
- package/engines/ferretdb/binary-urls.ts +0 -183
- package/engines/ferretdb/index.ts +0 -1907
- package/engines/ferretdb/restore.ts +0 -357
- package/engines/file-based-utils.ts +0 -262
- package/engines/index.ts +0 -131
- package/engines/influxdb/README.md +0 -180
- package/engines/influxdb/api-client.ts +0 -64
- package/engines/influxdb/backup.ts +0 -160
- package/engines/influxdb/binary-manager.ts +0 -110
- package/engines/influxdb/binary-urls.ts +0 -69
- package/engines/influxdb/hostdb-releases.ts +0 -23
- package/engines/influxdb/index.ts +0 -1272
- package/engines/influxdb/restore.ts +0 -417
- package/engines/influxdb/version-maps.ts +0 -75
- package/engines/influxdb/version-validator.ts +0 -128
- package/engines/mariadb/README.md +0 -141
- package/engines/mariadb/backup.ts +0 -233
- package/engines/mariadb/binary-manager.ts +0 -45
- package/engines/mariadb/hostdb-releases.ts +0 -23
- package/engines/mariadb/index.ts +0 -1300
- package/engines/mariadb/restore.ts +0 -447
- package/engines/mariadb/version-maps.ts +0 -72
- package/engines/mariadb/version-validator.ts +0 -181
- package/engines/meilisearch/README.md +0 -255
- package/engines/meilisearch/api-client.ts +0 -61
- package/engines/meilisearch/backup.ts +0 -233
- package/engines/meilisearch/binary-manager.ts +0 -43
- package/engines/meilisearch/binary-urls.ts +0 -69
- package/engines/meilisearch/hostdb-releases.ts +0 -26
- package/engines/meilisearch/index.ts +0 -1292
- package/engines/meilisearch/restore.ts +0 -219
- package/engines/meilisearch/version-maps.ts +0 -78
- package/engines/meilisearch/version-validator.ts +0 -128
- package/engines/mongodb/README.md +0 -162
- package/engines/mongodb/backup.ts +0 -127
- package/engines/mongodb/binary-manager.ts +0 -48
- package/engines/mongodb/binary-urls.ts +0 -63
- package/engines/mongodb/cli-utils.ts +0 -171
- package/engines/mongodb/hostdb-releases.ts +0 -91
- package/engines/mongodb/index.ts +0 -1118
- package/engines/mongodb/restore.ts +0 -361
- package/engines/mongodb/version-maps.ts +0 -91
- package/engines/mongodb/version-validator.ts +0 -160
- package/engines/mysql/README.md +0 -142
- package/engines/mysql/backup.ts +0 -270
- package/engines/mysql/binary-detection.ts +0 -408
- package/engines/mysql/binary-manager.ts +0 -42
- package/engines/mysql/binary-urls.ts +0 -104
- package/engines/mysql/index.ts +0 -1361
- package/engines/mysql/restore.ts +0 -500
- package/engines/mysql/version-maps.ts +0 -91
- package/engines/mysql/version-validator.ts +0 -369
- package/engines/postgresql/README.md +0 -158
- package/engines/postgresql/backup.ts +0 -151
- package/engines/postgresql/binary-manager.ts +0 -114
- package/engines/postgresql/binary-urls.ts +0 -99
- package/engines/postgresql/hostdb-releases.ts +0 -26
- package/engines/postgresql/index.ts +0 -1143
- package/engines/postgresql/remote-version.ts +0 -161
- package/engines/postgresql/restore.ts +0 -342
- package/engines/postgresql/version-maps.ts +0 -83
- package/engines/postgresql/version-validator.ts +0 -413
- package/engines/qdrant/README.md +0 -222
- package/engines/qdrant/api-client.ts +0 -61
- package/engines/qdrant/backup.ts +0 -165
- package/engines/qdrant/binary-manager.ts +0 -43
- package/engines/qdrant/binary-urls.ts +0 -115
- package/engines/qdrant/cli-utils.ts +0 -43
- package/engines/qdrant/hostdb-releases.ts +0 -23
- package/engines/qdrant/index.ts +0 -1312
- package/engines/qdrant/restore.ts +0 -203
- package/engines/qdrant/version-maps.ts +0 -78
- package/engines/qdrant/version-validator.ts +0 -128
- package/engines/questdb/README.md +0 -334
- package/engines/questdb/backup.ts +0 -220
- package/engines/questdb/binary-manager.ts +0 -310
- package/engines/questdb/binary-urls.ts +0 -34
- package/engines/questdb/hostdb-releases.ts +0 -23
- package/engines/questdb/index.ts +0 -1023
- package/engines/questdb/restore.ts +0 -260
- package/engines/questdb/version-maps.ts +0 -37
- package/engines/questdb/version-validator.ts +0 -121
- package/engines/redis/README.md +0 -173
- package/engines/redis/backup.ts +0 -389
- package/engines/redis/binary-manager.ts +0 -44
- package/engines/redis/binary-urls.ts +0 -117
- package/engines/redis/cli-utils.ts +0 -42
- package/engines/redis/hostdb-releases.ts +0 -23
- package/engines/redis/index.ts +0 -1583
- package/engines/redis/restore.ts +0 -443
- package/engines/redis/version-maps.ts +0 -81
- package/engines/redis/version-validator.ts +0 -131
- package/engines/sqlite/README.md +0 -162
- package/engines/sqlite/binary-manager.ts +0 -52
- package/engines/sqlite/hostdb-releases.ts +0 -23
- package/engines/sqlite/index.ts +0 -641
- package/engines/sqlite/registry.ts +0 -198
- package/engines/sqlite/scanner.ts +0 -22
- package/engines/sqlite/version-maps.ts +0 -64
- package/engines/surrealdb/README.md +0 -218
- package/engines/surrealdb/backup.ts +0 -131
- package/engines/surrealdb/binary-manager.ts +0 -45
- package/engines/surrealdb/binary-urls.ts +0 -40
- package/engines/surrealdb/cli-utils.ts +0 -173
- package/engines/surrealdb/hostdb-releases.ts +0 -23
- package/engines/surrealdb/index.ts +0 -1246
- package/engines/surrealdb/restore.ts +0 -302
- package/engines/surrealdb/version-maps.ts +0 -41
- package/engines/tigerbeetle/README.md +0 -61
- package/engines/tigerbeetle/backup.ts +0 -49
- package/engines/tigerbeetle/binary-manager.ts +0 -95
- package/engines/tigerbeetle/binary-urls.ts +0 -62
- package/engines/tigerbeetle/hostdb-releases.ts +0 -26
- package/engines/tigerbeetle/index.ts +0 -746
- package/engines/tigerbeetle/restore.ts +0 -130
- package/engines/tigerbeetle/version-validator.ts +0 -126
- package/engines/typedb/backup.ts +0 -167
- package/engines/typedb/binary-manager.ts +0 -200
- package/engines/typedb/binary-urls.ts +0 -40
- package/engines/typedb/cli-utils.ts +0 -210
- package/engines/typedb/hostdb-releases.ts +0 -23
- package/engines/typedb/index.ts +0 -1275
- package/engines/typedb/restore.ts +0 -377
- package/engines/typedb/version-maps.ts +0 -48
- package/engines/typedb/version-validator.ts +0 -127
- package/engines/valkey/README.md +0 -219
- package/engines/valkey/backup.ts +0 -389
- package/engines/valkey/binary-manager.ts +0 -45
- package/engines/valkey/binary-urls.ts +0 -122
- package/engines/valkey/cli-utils.ts +0 -42
- package/engines/valkey/hostdb-releases.ts +0 -23
- package/engines/valkey/index.ts +0 -1585
- package/engines/valkey/restore.ts +0 -446
- package/engines/valkey/version-maps.ts +0 -81
- package/engines/valkey/version-validator.ts +0 -131
- package/engines/weaviate/README.md +0 -302
- package/engines/weaviate/api-client.ts +0 -61
- package/engines/weaviate/backup.ts +0 -145
- package/engines/weaviate/binary-manager.ts +0 -80
- package/engines/weaviate/binary-urls.ts +0 -115
- package/engines/weaviate/cli-utils.ts +0 -43
- package/engines/weaviate/hostdb-releases.ts +0 -23
- package/engines/weaviate/index.ts +0 -1139
- package/engines/weaviate/restore.ts +0 -235
- package/engines/weaviate/version-maps.ts +0 -78
- package/engines/weaviate/version-validator.ts +0 -128
- package/types/index.ts +0 -624
- /package/{config → dist/config}/engines.json +0 -0
|
@@ -1,1907 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FerretDB Engine implementation
|
|
3
|
-
*
|
|
4
|
-
* FerretDB is a MongoDB-compatible proxy that stores data in PostgreSQL.
|
|
5
|
-
* This is a composite engine that manages two processes:
|
|
6
|
-
* 1. PostgreSQL backend (postgresql-documentdb)
|
|
7
|
-
* 2. FerretDB proxy
|
|
8
|
-
*
|
|
9
|
-
* The lifecycle is:
|
|
10
|
-
* - Start: Start PostgreSQL → Wait for ready → Start FerretDB
|
|
11
|
-
* - Stop: Stop FerretDB → Stop PostgreSQL
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
spawn,
|
|
16
|
-
exec,
|
|
17
|
-
type ChildProcess,
|
|
18
|
-
type SpawnOptions,
|
|
19
|
-
} from 'child_process'
|
|
20
|
-
import { promisify } from 'util'
|
|
21
|
-
import { existsSync } from 'fs'
|
|
22
|
-
import net from 'net'
|
|
23
|
-
import {
|
|
24
|
-
mkdir,
|
|
25
|
-
writeFile,
|
|
26
|
-
readFile,
|
|
27
|
-
symlink,
|
|
28
|
-
unlink,
|
|
29
|
-
readdir,
|
|
30
|
-
} from 'fs/promises'
|
|
31
|
-
import { join, basename, dirname } from 'path'
|
|
32
|
-
import { BaseEngine } from '../base-engine'
|
|
33
|
-
import { paths } from '../../config/paths'
|
|
34
|
-
import { getEngineDefaults } from '../../config/defaults'
|
|
35
|
-
import { platformService, isWindows } from '../../core/platform-service'
|
|
36
|
-
import { configManager } from '../../core/config-manager'
|
|
37
|
-
import { containerManager } from '../../core/container-manager'
|
|
38
|
-
import {
|
|
39
|
-
logDebug,
|
|
40
|
-
logWarning,
|
|
41
|
-
assertValidDatabaseName,
|
|
42
|
-
assertValidUsername,
|
|
43
|
-
} from '../../core/error-handler'
|
|
44
|
-
import { processManager } from '../../core/process-manager'
|
|
45
|
-
import { spawnAsync } from '../../core/spawn-utils'
|
|
46
|
-
import { ferretdbBinaryManager } from './binary-manager'
|
|
47
|
-
import {
|
|
48
|
-
SUPPORTED_MAJOR_VERSIONS,
|
|
49
|
-
FALLBACK_VERSION_MAP,
|
|
50
|
-
DEFAULT_DOCUMENTDB_VERSION,
|
|
51
|
-
DEFAULT_V1_POSTGRESQL_VERSION,
|
|
52
|
-
normalizeVersion,
|
|
53
|
-
normalizeDocumentDBVersion,
|
|
54
|
-
isV1,
|
|
55
|
-
} from './version-maps'
|
|
56
|
-
import { getBinaryUrls, isPlatformSupported } from './binary-urls'
|
|
57
|
-
import {
|
|
58
|
-
detectBackupFormat as detectBackupFormatImpl,
|
|
59
|
-
restoreBackup,
|
|
60
|
-
} from './restore'
|
|
61
|
-
import { createBackup } from './backup'
|
|
62
|
-
import {
|
|
63
|
-
type Platform,
|
|
64
|
-
type Arch,
|
|
65
|
-
type ContainerConfig,
|
|
66
|
-
type ProgressCallback,
|
|
67
|
-
type BackupFormat,
|
|
68
|
-
type BackupOptions,
|
|
69
|
-
type BackupResult,
|
|
70
|
-
type RestoreResult,
|
|
71
|
-
type DumpResult,
|
|
72
|
-
type StatusResult,
|
|
73
|
-
type QueryResult,
|
|
74
|
-
type QueryOptions,
|
|
75
|
-
type CreateUserOptions,
|
|
76
|
-
type UserCredentials,
|
|
77
|
-
} from '../../types'
|
|
78
|
-
import { parseMongoDBResult } from '../../core/query-parser'
|
|
79
|
-
|
|
80
|
-
const execAsync = promisify(exec)
|
|
81
|
-
|
|
82
|
-
const ENGINE = 'ferretdb'
|
|
83
|
-
const engineDef = getEngineDefaults(ENGINE)
|
|
84
|
-
|
|
85
|
-
// Default internal PostgreSQL port range for FerretDB backends
|
|
86
|
-
const BACKEND_PORT_START = 54320
|
|
87
|
-
const BACKEND_PORT_END = 54400
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Allocate a port for the PostgreSQL backend
|
|
91
|
-
*
|
|
92
|
-
* KNOWN LIMITATION (TOCTOU): There is a race condition between checking port
|
|
93
|
-
* availability and PostgreSQL actually binding to the port. Another process
|
|
94
|
-
* could claim the port in between. This is acceptable for SpinDB's use case:
|
|
95
|
-
*
|
|
96
|
-
* 1. The backend port range (54320-54400) is unlikely to be used by other apps
|
|
97
|
-
* 2. SpinDB is a local development tool, not a production server
|
|
98
|
-
* 3. If a collision occurs, PostgreSQL will fail to start with a clear error
|
|
99
|
-
* 4. The CLI layer uses startWithRetry() which can re-attempt with a new port
|
|
100
|
-
*
|
|
101
|
-
* A more robust solution would use SO_REUSEPORT or hold the socket until
|
|
102
|
-
* PostgreSQL starts, but this adds complexity for minimal real-world benefit.
|
|
103
|
-
*/
|
|
104
|
-
async function allocateBackendPort(): Promise<number> {
|
|
105
|
-
for (let port = BACKEND_PORT_START; port <= BACKEND_PORT_END; port++) {
|
|
106
|
-
if (await isPortAvailable(port)) {
|
|
107
|
-
return port
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
throw new Error(
|
|
111
|
-
`No available ports in range ${BACKEND_PORT_START}-${BACKEND_PORT_END} for PostgreSQL backend`,
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Check if a port is available
|
|
117
|
-
*/
|
|
118
|
-
function isPortAvailable(port: number): Promise<boolean> {
|
|
119
|
-
return new Promise((resolve) => {
|
|
120
|
-
const server = net.createServer()
|
|
121
|
-
server.once('error', () => resolve(false))
|
|
122
|
-
server.once('listening', () => {
|
|
123
|
-
server.close()
|
|
124
|
-
resolve(true)
|
|
125
|
-
})
|
|
126
|
-
server.listen(port, '127.0.0.1')
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Wait for a TCP port to accept connections
|
|
132
|
-
*/
|
|
133
|
-
function waitForPort(port: number, timeoutMs = 30000): Promise<boolean> {
|
|
134
|
-
const startTime = Date.now()
|
|
135
|
-
const checkInterval = 500
|
|
136
|
-
|
|
137
|
-
return new Promise((resolve) => {
|
|
138
|
-
const check = () => {
|
|
139
|
-
const socket = new net.Socket()
|
|
140
|
-
socket.setTimeout(1000)
|
|
141
|
-
socket.once('connect', () => {
|
|
142
|
-
socket.destroy()
|
|
143
|
-
resolve(true)
|
|
144
|
-
})
|
|
145
|
-
socket.once('error', () => {
|
|
146
|
-
socket.destroy()
|
|
147
|
-
if (Date.now() - startTime < timeoutMs) {
|
|
148
|
-
setTimeout(check, checkInterval)
|
|
149
|
-
} else {
|
|
150
|
-
resolve(false)
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
socket.once('timeout', () => {
|
|
154
|
-
socket.destroy()
|
|
155
|
-
if (Date.now() - startTime < timeoutMs) {
|
|
156
|
-
setTimeout(check, checkInterval)
|
|
157
|
-
} else {
|
|
158
|
-
resolve(false)
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
socket.connect(port, '127.0.0.1')
|
|
162
|
-
}
|
|
163
|
-
check()
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Spawn a process and pipe input to its stdin.
|
|
169
|
-
* Used for `postgres --single` which reads SQL from stdin.
|
|
170
|
-
*/
|
|
171
|
-
function spawnWithInput(
|
|
172
|
-
command: string,
|
|
173
|
-
args: string[],
|
|
174
|
-
input: string,
|
|
175
|
-
options?: { env?: Record<string, string>; timeout?: number },
|
|
176
|
-
): Promise<{ stdout: string; stderr: string }> {
|
|
177
|
-
return new Promise((resolve, reject) => {
|
|
178
|
-
let proc: ChildProcess
|
|
179
|
-
try {
|
|
180
|
-
proc = spawn(command, args, {
|
|
181
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
182
|
-
env: options?.env ? { ...process.env, ...options.env } : undefined,
|
|
183
|
-
})
|
|
184
|
-
} catch (error) {
|
|
185
|
-
reject(error)
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
let stdout = ''
|
|
190
|
-
let stderr = ''
|
|
191
|
-
let timedOut = false
|
|
192
|
-
|
|
193
|
-
const timer = options?.timeout
|
|
194
|
-
? setTimeout(() => {
|
|
195
|
-
timedOut = true
|
|
196
|
-
proc.kill('SIGKILL')
|
|
197
|
-
reject(
|
|
198
|
-
new Error(
|
|
199
|
-
`Command "${command}" timed out after ${options.timeout}ms`,
|
|
200
|
-
),
|
|
201
|
-
)
|
|
202
|
-
}, options.timeout)
|
|
203
|
-
: undefined
|
|
204
|
-
|
|
205
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
206
|
-
stdout += data.toString()
|
|
207
|
-
})
|
|
208
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
209
|
-
stderr += data.toString()
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
proc.on('close', (code) => {
|
|
213
|
-
if (timer) clearTimeout(timer)
|
|
214
|
-
if (timedOut) return
|
|
215
|
-
if (code === 0) {
|
|
216
|
-
resolve({ stdout, stderr })
|
|
217
|
-
} else {
|
|
218
|
-
reject(
|
|
219
|
-
new Error(
|
|
220
|
-
`Command "${command}" failed with code ${code}: ${stderr || stdout}`,
|
|
221
|
-
),
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
proc.on('error', (err) => {
|
|
227
|
-
if (timer) clearTimeout(timer)
|
|
228
|
-
if (timedOut) return
|
|
229
|
-
reject(err)
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
proc.stdin?.write(input)
|
|
233
|
-
proc.stdin?.end()
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export class FerretDBEngine extends BaseEngine {
|
|
238
|
-
name = ENGINE
|
|
239
|
-
displayName = 'FerretDB'
|
|
240
|
-
defaultPort = engineDef.defaultPort
|
|
241
|
-
supportedVersions = SUPPORTED_MAJOR_VERSIONS
|
|
242
|
-
|
|
243
|
-
// Get the current platform and architecture
|
|
244
|
-
getPlatformInfo(): { platform: Platform; arch: Arch } {
|
|
245
|
-
return platformService.getPlatformInfo()
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Check if the current platform supports FerretDB
|
|
250
|
-
* @param version - Optional version to check (v1 supports Windows, v2 does not)
|
|
251
|
-
*/
|
|
252
|
-
isPlatformSupported(version?: string): boolean {
|
|
253
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
254
|
-
return isPlatformSupported(platform, arch, version)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Returns available FerretDB versions from the fallback version map.
|
|
259
|
-
*/
|
|
260
|
-
async fetchAvailableVersions(): Promise<Record<string, string[]>> {
|
|
261
|
-
const versions: Record<string, string[]> = {}
|
|
262
|
-
|
|
263
|
-
for (const [major, full] of Object.entries(FALLBACK_VERSION_MAP)) {
|
|
264
|
-
if (/^\d+$/.test(major)) {
|
|
265
|
-
versions[major] = [full]
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return versions
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Get binary download URL from hostdb
|
|
273
|
-
getBinaryUrl(version: string, platform: Platform, arch: Arch): string {
|
|
274
|
-
const backendVersion = isV1(version)
|
|
275
|
-
? DEFAULT_V1_POSTGRESQL_VERSION
|
|
276
|
-
: DEFAULT_DOCUMENTDB_VERSION
|
|
277
|
-
const urls = getBinaryUrls(version, backendVersion, platform, arch)
|
|
278
|
-
return urls.ferretdb
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Resolves version string to full version
|
|
282
|
-
resolveFullVersion(version: string): string {
|
|
283
|
-
if (/^\d+\.\d+\.\d+$/.test(version)) {
|
|
284
|
-
return version
|
|
285
|
-
}
|
|
286
|
-
return FALLBACK_VERSION_MAP[version] || `${version}.0.0`
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Get the path where binaries for a version would be installed
|
|
290
|
-
getBinaryPath(version: string): string {
|
|
291
|
-
const fullVersion = this.resolveFullVersion(version)
|
|
292
|
-
const { platform: p, arch: a } = this.getPlatformInfo()
|
|
293
|
-
return ferretdbBinaryManager.getFerretDBBinaryPath(fullVersion, p, a)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Verify that FerretDB binaries are available and functional
|
|
298
|
-
*/
|
|
299
|
-
async verifyBinary(binPath: string, version?: string): Promise<boolean> {
|
|
300
|
-
const { platform: p, arch: a } = this.getPlatformInfo()
|
|
301
|
-
|
|
302
|
-
if (version) {
|
|
303
|
-
const backendVersion = isV1(version)
|
|
304
|
-
? DEFAULT_V1_POSTGRESQL_VERSION
|
|
305
|
-
: DEFAULT_DOCUMENTDB_VERSION
|
|
306
|
-
return ferretdbBinaryManager.isInstalled(version, p, a, backendVersion)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Fallback: extract version from directory name
|
|
310
|
-
const dirName = basename(binPath)
|
|
311
|
-
const match = dirName.match(/^ferretdb-([\d.]+)-/)
|
|
312
|
-
if (match) {
|
|
313
|
-
const extractedVersion = match[1]
|
|
314
|
-
const backendVersion = isV1(extractedVersion)
|
|
315
|
-
? DEFAULT_V1_POSTGRESQL_VERSION
|
|
316
|
-
: DEFAULT_DOCUMENTDB_VERSION
|
|
317
|
-
return ferretdbBinaryManager.isInstalled(
|
|
318
|
-
extractedVersion,
|
|
319
|
-
p,
|
|
320
|
-
a,
|
|
321
|
-
backendVersion,
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Last resort: check file existence
|
|
326
|
-
const ext = platformService.getExecutableExtension()
|
|
327
|
-
const ferretdbPath = join(binPath, 'bin', `ferretdb${ext}`)
|
|
328
|
-
return existsSync(ferretdbPath)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Check if a specific FerretDB version is installed
|
|
332
|
-
async isBinaryInstalled(version: string): Promise<boolean> {
|
|
333
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
334
|
-
const backendVersion = isV1(version)
|
|
335
|
-
? DEFAULT_V1_POSTGRESQL_VERSION
|
|
336
|
-
: DEFAULT_DOCUMENTDB_VERSION
|
|
337
|
-
return ferretdbBinaryManager.isInstalled(
|
|
338
|
-
version,
|
|
339
|
-
platform,
|
|
340
|
-
arch,
|
|
341
|
-
backendVersion,
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Ensure FerretDB binaries are available for a specific version
|
|
347
|
-
*/
|
|
348
|
-
async ensureBinaries(
|
|
349
|
-
version: string,
|
|
350
|
-
onProgress?: ProgressCallback,
|
|
351
|
-
): Promise<string> {
|
|
352
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
353
|
-
const backendVersion = isV1(version)
|
|
354
|
-
? DEFAULT_V1_POSTGRESQL_VERSION
|
|
355
|
-
: DEFAULT_DOCUMENTDB_VERSION
|
|
356
|
-
|
|
357
|
-
// Download binaries (proxy + backend)
|
|
358
|
-
const { ferretdbPath } = await ferretdbBinaryManager.ensureInstalled(
|
|
359
|
-
version,
|
|
360
|
-
platform,
|
|
361
|
-
arch,
|
|
362
|
-
onProgress,
|
|
363
|
-
backendVersion,
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
// Register ferretdb binary in config
|
|
367
|
-
const ext = platformService.getExecutableExtension()
|
|
368
|
-
const ferretdbBinary = join(ferretdbPath, 'bin', `ferretdb${ext}`)
|
|
369
|
-
if (existsSync(ferretdbBinary)) {
|
|
370
|
-
await configManager.setBinaryPath('ferretdb', ferretdbBinary, 'bundled')
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return ferretdbPath
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Get backend binary paths and spawn environment for a container.
|
|
378
|
-
* Centralizes the v1/v2 branching logic for backend resolution.
|
|
379
|
-
*/
|
|
380
|
-
private getBackendPaths(
|
|
381
|
-
version: string,
|
|
382
|
-
backendVersion: string,
|
|
383
|
-
platform: Platform,
|
|
384
|
-
arch: Arch,
|
|
385
|
-
): { backendPath: string; pgSpawnEnv: Record<string, string> | undefined } {
|
|
386
|
-
const fullVersion = normalizeVersion(version)
|
|
387
|
-
const backendPath = ferretdbBinaryManager.getBackendBinaryPath(
|
|
388
|
-
fullVersion,
|
|
389
|
-
backendVersion,
|
|
390
|
-
platform,
|
|
391
|
-
arch,
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
const baseSpawnEnv = ferretdbBinaryManager.getBackendSpawnEnv(
|
|
395
|
-
fullVersion,
|
|
396
|
-
backendVersion,
|
|
397
|
-
platform,
|
|
398
|
-
arch,
|
|
399
|
-
)
|
|
400
|
-
const pgSpawnEnv =
|
|
401
|
-
platform === 'darwin'
|
|
402
|
-
? {
|
|
403
|
-
...baseSpawnEnv,
|
|
404
|
-
DYLD_FALLBACK_LIBRARY_PATH: join(backendPath, 'lib'),
|
|
405
|
-
}
|
|
406
|
-
: baseSpawnEnv
|
|
407
|
-
|
|
408
|
-
return { backendPath, pgSpawnEnv }
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Initialize a new FerretDB container directory
|
|
413
|
-
* Creates both the PostgreSQL data directory and FerretDB config
|
|
414
|
-
*/
|
|
415
|
-
async initDataDir(
|
|
416
|
-
containerName: string,
|
|
417
|
-
_version: string,
|
|
418
|
-
options: Record<string, unknown> = {},
|
|
419
|
-
): Promise<string> {
|
|
420
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
421
|
-
const version = normalizeVersion(_version)
|
|
422
|
-
const v1 = isV1(version)
|
|
423
|
-
|
|
424
|
-
// Get binary paths - resolve backend based on v1/v2
|
|
425
|
-
const backendVersion = v1
|
|
426
|
-
? (options.backendVersion as string) || DEFAULT_V1_POSTGRESQL_VERSION
|
|
427
|
-
: (options.backendVersion as string) || DEFAULT_DOCUMENTDB_VERSION
|
|
428
|
-
|
|
429
|
-
const { backendPath: documentdbPath, pgSpawnEnv: initSpawnEnv } =
|
|
430
|
-
this.getBackendPaths(version, backendVersion, platform, arch)
|
|
431
|
-
|
|
432
|
-
// Container directory structure
|
|
433
|
-
const containerDir = paths.getContainerPath(containerName, {
|
|
434
|
-
engine: ENGINE,
|
|
435
|
-
})
|
|
436
|
-
const pgDataDir = join(containerDir, 'pg_data')
|
|
437
|
-
const logsDir = join(containerDir, 'logs')
|
|
438
|
-
|
|
439
|
-
// Create directories
|
|
440
|
-
await mkdir(containerDir, { recursive: true })
|
|
441
|
-
await mkdir(logsDir, { recursive: true })
|
|
442
|
-
|
|
443
|
-
// Initialize PostgreSQL data directory
|
|
444
|
-
// Check for PG_VERSION file to determine if already initialized
|
|
445
|
-
// (directory may exist but be empty if created by containerManager.create)
|
|
446
|
-
const pgVersionFile = join(pgDataDir, 'PG_VERSION')
|
|
447
|
-
if (!existsSync(pgVersionFile)) {
|
|
448
|
-
const ext = platformService.getExecutableExtension()
|
|
449
|
-
const initdb = join(documentdbPath, 'bin', `initdb${ext}`)
|
|
450
|
-
|
|
451
|
-
if (!existsSync(initdb)) {
|
|
452
|
-
throw new Error(`initdb not found at ${initdb}`)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Homebrew-derived x64 binaries have compiled-in absolute paths for
|
|
456
|
-
// sharedir, pkglibdir ($libdir), and libdir that don't exist when running
|
|
457
|
-
// from ~/.spindb/bin/. We fix this by:
|
|
458
|
-
// 1. Using initdb's -L flag to explicitly set the share directory
|
|
459
|
-
// 2. Creating symlinks at compiled-in paths for pkglibdir and libdir
|
|
460
|
-
// (these are needed by the bootstrap postgres subprocess during initdb)
|
|
461
|
-
const shareDirBase = join(documentdbPath, 'share')
|
|
462
|
-
const actualShareDir = existsSync(join(shareDirBase, 'postgres.bki'))
|
|
463
|
-
? shareDirBase
|
|
464
|
-
: existsSync(join(shareDirBase, 'postgresql', 'postgres.bki'))
|
|
465
|
-
? join(shareDirBase, 'postgresql')
|
|
466
|
-
: shareDirBase
|
|
467
|
-
|
|
468
|
-
// v2 only: Homebrew-derived DocumentDB binaries need compiled-in path fixups
|
|
469
|
-
// v1 uses plain PostgreSQL which has correct relative paths
|
|
470
|
-
if (!v1 && platform === 'darwin') {
|
|
471
|
-
const pgConfigBin = join(documentdbPath, 'bin', `pg_config${ext}`)
|
|
472
|
-
if (existsSync(pgConfigBin)) {
|
|
473
|
-
// Query all relevant compiled-in paths and create symlinks where needed
|
|
474
|
-
const pathFixups: Array<{
|
|
475
|
-
flag: string
|
|
476
|
-
actualDir: string
|
|
477
|
-
label: string
|
|
478
|
-
}> = [
|
|
479
|
-
{ flag: '--sharedir', actualDir: actualShareDir, label: 'share' },
|
|
480
|
-
{
|
|
481
|
-
flag: '--pkglibdir',
|
|
482
|
-
actualDir: existsSync(join(documentdbPath, 'lib', 'postgresql'))
|
|
483
|
-
? join(documentdbPath, 'lib', 'postgresql')
|
|
484
|
-
: join(documentdbPath, 'lib'),
|
|
485
|
-
label: 'pkglib',
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
flag: '--libdir',
|
|
489
|
-
actualDir: join(documentdbPath, 'lib'),
|
|
490
|
-
label: 'lib',
|
|
491
|
-
},
|
|
492
|
-
]
|
|
493
|
-
|
|
494
|
-
// Create symlinks at compiled-in paths so PostgreSQL can find its
|
|
495
|
-
// libraries. These paths may be in system directories (e.g. /usr/local/),
|
|
496
|
-
// which require elevated privileges to write to.
|
|
497
|
-
for (const { flag, actualDir, label } of pathFixups) {
|
|
498
|
-
try {
|
|
499
|
-
const { stdout: out } = await execAsync(
|
|
500
|
-
`"${pgConfigBin}" ${flag}`,
|
|
501
|
-
{ timeout: 5000 },
|
|
502
|
-
)
|
|
503
|
-
const compiledDir = out.trim()
|
|
504
|
-
logDebug(`pg_config ${flag}: ${compiledDir}`)
|
|
505
|
-
if (compiledDir && !existsSync(compiledDir)) {
|
|
506
|
-
await mkdir(dirname(compiledDir), { recursive: true })
|
|
507
|
-
await symlink(actualDir, compiledDir)
|
|
508
|
-
logDebug(
|
|
509
|
-
`Created ${label} symlink: ${compiledDir} -> ${actualDir}`,
|
|
510
|
-
)
|
|
511
|
-
}
|
|
512
|
-
} catch (error) {
|
|
513
|
-
const e = error as NodeJS.ErrnoException
|
|
514
|
-
const isPermission =
|
|
515
|
-
e.code === 'EACCES' ||
|
|
516
|
-
e.code === 'EPERM' ||
|
|
517
|
-
(e.message && /permission denied/i.test(e.message))
|
|
518
|
-
if (isPermission) {
|
|
519
|
-
logWarning(
|
|
520
|
-
`Cannot create ${label} symlink (permission denied). ` +
|
|
521
|
-
`This can be caused by macOS SIP or container/sudo limitations when compiled-in paths point to system directories. ` +
|
|
522
|
-
`Workaround: use a non-system install path, or run with elevated privileges if available (e.g., sudo spindb engines download ferretdb <version>). ` +
|
|
523
|
-
`See https://github.com/robertjbass/spindb#ferretdb for details. ` +
|
|
524
|
-
`Target: ${flag} -> ${actualDir}`,
|
|
525
|
-
)
|
|
526
|
-
} else {
|
|
527
|
-
logDebug(`Could not fix compiled ${label} path: ${e.message}`)
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
} else if (!v1) {
|
|
533
|
-
logDebug(
|
|
534
|
-
'Skipping pg_config symlink fixups (not required on this platform)',
|
|
535
|
-
)
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// v2 only: Fix hardcoded Homebrew dylib paths in DocumentDB extension libraries
|
|
539
|
-
// v1 uses plain PostgreSQL which doesn't have DocumentDB extensions
|
|
540
|
-
if (!v1 && platform === 'darwin') {
|
|
541
|
-
const dylibMarker = join(documentdbPath, '.dylib_fix_done')
|
|
542
|
-
if (!existsSync(dylibMarker)) {
|
|
543
|
-
await this.fixDylibDependencies(documentdbPath)
|
|
544
|
-
try {
|
|
545
|
-
await writeFile(dylibMarker, '', { flag: 'wx' })
|
|
546
|
-
} catch {
|
|
547
|
-
// Marker may already exist from a parallel init — safe to ignore
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
try {
|
|
553
|
-
await spawnAsync(
|
|
554
|
-
initdb,
|
|
555
|
-
[
|
|
556
|
-
'-D',
|
|
557
|
-
pgDataDir,
|
|
558
|
-
'-U',
|
|
559
|
-
'postgres',
|
|
560
|
-
'--encoding=UTF8',
|
|
561
|
-
'--locale=C',
|
|
562
|
-
'-L',
|
|
563
|
-
actualShareDir,
|
|
564
|
-
],
|
|
565
|
-
{ env: initSpawnEnv, timeout: 60000 },
|
|
566
|
-
)
|
|
567
|
-
logDebug(`Initialized PostgreSQL data directory: ${pgDataDir}`)
|
|
568
|
-
} catch (error) {
|
|
569
|
-
const err = error as Error
|
|
570
|
-
throw new Error(`Failed to initialize PostgreSQL: ${err.message}`)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// v2 only: Copy the bundled postgresql.conf.sample to ensure shared_preload_libraries is set
|
|
574
|
-
// This is critical for DocumentDB extension to load properly
|
|
575
|
-
// v1 uses initdb defaults (no DocumentDB extensions to preload)
|
|
576
|
-
if (!v1) {
|
|
577
|
-
const bundledConf = existsSync(
|
|
578
|
-
join(shareDirBase, 'postgresql.conf.sample'),
|
|
579
|
-
)
|
|
580
|
-
? join(shareDirBase, 'postgresql.conf.sample')
|
|
581
|
-
: join(shareDirBase, 'postgresql', 'postgresql.conf.sample')
|
|
582
|
-
const pgConf = join(pgDataDir, 'postgresql.conf')
|
|
583
|
-
|
|
584
|
-
if (existsSync(bundledConf)) {
|
|
585
|
-
try {
|
|
586
|
-
// Read the bundled config
|
|
587
|
-
let confContent = await readFile(bundledConf, 'utf8')
|
|
588
|
-
|
|
589
|
-
// Update cron.database_name to 'ferretdb' (required for pg_cron to work with DocumentDB)
|
|
590
|
-
confContent = confContent.replace(
|
|
591
|
-
/cron\.database_name\s*=\s*'[^']*'/,
|
|
592
|
-
"cron.database_name = 'ferretdb'",
|
|
593
|
-
)
|
|
594
|
-
|
|
595
|
-
// Write the modified config
|
|
596
|
-
await writeFile(pgConf, confContent)
|
|
597
|
-
logDebug(`Copied and configured postgresql.conf to ${pgConf}`)
|
|
598
|
-
} catch (copyError) {
|
|
599
|
-
logDebug(
|
|
600
|
-
`Warning: Could not copy postgresql.conf.sample: ${copyError}`,
|
|
601
|
-
)
|
|
602
|
-
// Continue anyway - initdb creates a default config
|
|
603
|
-
}
|
|
604
|
-
} else {
|
|
605
|
-
logDebug(`Bundled postgresql.conf.sample not found at ${bundledConf}`)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
return pgDataDir
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Start FerretDB (two-process lifecycle)
|
|
615
|
-
*
|
|
616
|
-
* 1. Allocate/verify backend port
|
|
617
|
-
* 2. Start PostgreSQL
|
|
618
|
-
* 3. Wait for PostgreSQL ready
|
|
619
|
-
* 4. Create ferretdb database + extension (first start)
|
|
620
|
-
* 5. Start FerretDB proxy
|
|
621
|
-
* 6. Verify FerretDB connectivity
|
|
622
|
-
*/
|
|
623
|
-
async start(
|
|
624
|
-
container: ContainerConfig,
|
|
625
|
-
onProgress?: ProgressCallback,
|
|
626
|
-
): Promise<{ port: number; connectionString: string }> {
|
|
627
|
-
const {
|
|
628
|
-
name,
|
|
629
|
-
port,
|
|
630
|
-
version,
|
|
631
|
-
backendVersion,
|
|
632
|
-
backendPort: existingBackendPort,
|
|
633
|
-
} = container
|
|
634
|
-
|
|
635
|
-
// Check if already running
|
|
636
|
-
const alreadyRunning = await processManager.isRunning(name, {
|
|
637
|
-
engine: ENGINE,
|
|
638
|
-
})
|
|
639
|
-
if (alreadyRunning) {
|
|
640
|
-
return {
|
|
641
|
-
port,
|
|
642
|
-
connectionString: this.getConnectionString(container),
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
647
|
-
const fullVersion = normalizeVersion(version)
|
|
648
|
-
const v1 = isV1(version)
|
|
649
|
-
const effectiveBackendVersion = v1
|
|
650
|
-
? backendVersion || DEFAULT_V1_POSTGRESQL_VERSION
|
|
651
|
-
: normalizeDocumentDBVersion(backendVersion || DEFAULT_DOCUMENTDB_VERSION)
|
|
652
|
-
|
|
653
|
-
// Get binary paths using version-aware helper
|
|
654
|
-
const ferretdbPath = ferretdbBinaryManager.getFerretDBBinaryPath(
|
|
655
|
-
fullVersion,
|
|
656
|
-
platform,
|
|
657
|
-
arch,
|
|
658
|
-
)
|
|
659
|
-
const { backendPath: documentdbPath, pgSpawnEnv } = this.getBackendPaths(
|
|
660
|
-
version,
|
|
661
|
-
effectiveBackendVersion,
|
|
662
|
-
platform,
|
|
663
|
-
arch,
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
const ext = platformService.getExecutableExtension()
|
|
667
|
-
const ferretdbBinary = join(ferretdbPath, 'bin', `ferretdb${ext}`)
|
|
668
|
-
const pgCtl = join(documentdbPath, 'bin', `pg_ctl${ext}`)
|
|
669
|
-
// v1 backend may be a minimal PostgreSQL install (shared with DocumentDB) that
|
|
670
|
-
// lacks client tools. Use postgres --single as fallback for database creation.
|
|
671
|
-
const psqlCandidate = join(documentdbPath, 'bin', `psql${ext}`)
|
|
672
|
-
const psql = existsSync(psqlCandidate) ? psqlCandidate : null
|
|
673
|
-
const postgresBinary = join(documentdbPath, 'bin', `postgres${ext}`)
|
|
674
|
-
|
|
675
|
-
// Verify binaries exist
|
|
676
|
-
if (!existsSync(ferretdbBinary)) {
|
|
677
|
-
throw new Error(
|
|
678
|
-
`FerretDB binary not found. Run: spindb engines download ferretdb ${version}`,
|
|
679
|
-
)
|
|
680
|
-
}
|
|
681
|
-
if (!existsSync(pgCtl)) {
|
|
682
|
-
throw new Error(
|
|
683
|
-
`postgresql-documentdb not found. Run: spindb engines download ferretdb ${version}`,
|
|
684
|
-
)
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Container paths
|
|
688
|
-
const containerDir = paths.getContainerPath(name, { engine: ENGINE })
|
|
689
|
-
const pgDataDir = join(containerDir, 'pg_data')
|
|
690
|
-
const logsDir = join(containerDir, 'logs')
|
|
691
|
-
const pgLogFile = join(logsDir, 'postgres.log')
|
|
692
|
-
const ferretPidFile = join(containerDir, 'ferretdb.pid')
|
|
693
|
-
|
|
694
|
-
// Allocate backend port
|
|
695
|
-
const backendPort = existingBackendPort || (await allocateBackendPort())
|
|
696
|
-
|
|
697
|
-
// v2 only: Fix hardcoded Homebrew dylib paths (darwin-x64 binaries)
|
|
698
|
-
// Skip if already completed (marker written by initDataDir or a previous start)
|
|
699
|
-
// v1 uses plain PostgreSQL which doesn't have DocumentDB extensions
|
|
700
|
-
if (!v1 && platform === 'darwin') {
|
|
701
|
-
const dylibMarker = join(documentdbPath, '.dylib_fix_done')
|
|
702
|
-
if (!existsSync(dylibMarker)) {
|
|
703
|
-
await this.fixDylibDependencies(documentdbPath)
|
|
704
|
-
try {
|
|
705
|
-
await writeFile(dylibMarker, '', { flag: 'wx' })
|
|
706
|
-
} catch {
|
|
707
|
-
// Marker may already exist — safe to ignore
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
let pgStarted = false
|
|
713
|
-
let ferretStarted = false
|
|
714
|
-
|
|
715
|
-
try {
|
|
716
|
-
// 1. Start PostgreSQL (skip if already running)
|
|
717
|
-
onProgress?.({
|
|
718
|
-
stage: 'starting',
|
|
719
|
-
message: 'Starting PostgreSQL backend...',
|
|
720
|
-
})
|
|
721
|
-
|
|
722
|
-
// Check if PostgreSQL backend is already running in this data dir
|
|
723
|
-
let pgAlreadyRunning = false
|
|
724
|
-
try {
|
|
725
|
-
await spawnAsync(pgCtl, ['status', '-D', pgDataDir], {
|
|
726
|
-
env: pgSpawnEnv,
|
|
727
|
-
timeout: 5000,
|
|
728
|
-
})
|
|
729
|
-
// pg_ctl status exits 0 if server is running
|
|
730
|
-
pgAlreadyRunning = true
|
|
731
|
-
logDebug('PostgreSQL backend already running, skipping start')
|
|
732
|
-
} catch {
|
|
733
|
-
// Exit code != 0 means not running — proceed to start
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// v1 pre-start: Create ferretdb database using postgres --single mode
|
|
737
|
-
// when psql is unavailable (minimal PG install may lack client tools).
|
|
738
|
-
// postgres --single requires exclusive data dir access, so this MUST
|
|
739
|
-
// happen before pg_ctl start.
|
|
740
|
-
if (v1 && !psql && !pgAlreadyRunning) {
|
|
741
|
-
logDebug(
|
|
742
|
-
'psql not found in backend, using postgres --single to pre-create database',
|
|
743
|
-
)
|
|
744
|
-
try {
|
|
745
|
-
await spawnWithInput(
|
|
746
|
-
postgresBinary,
|
|
747
|
-
['--single', '-D', pgDataDir, 'postgres'],
|
|
748
|
-
"CREATE DATABASE ferretdb ENCODING 'UTF8';\n",
|
|
749
|
-
{ env: pgSpawnEnv, timeout: 30000 },
|
|
750
|
-
)
|
|
751
|
-
logDebug('Pre-created ferretdb database via postgres --single')
|
|
752
|
-
} catch {
|
|
753
|
-
// Database may already exist from a previous start — safe to ignore
|
|
754
|
-
logDebug(
|
|
755
|
-
'postgres --single CREATE DATABASE failed (may already exist)',
|
|
756
|
-
)
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (!pgAlreadyRunning) {
|
|
761
|
-
// Use pg_ctl to start PostgreSQL
|
|
762
|
-
// On Windows, spawnAsync pipes stdout/stderr which get inherited by the
|
|
763
|
-
// PostgreSQL background process, preventing the 'close' event from firing
|
|
764
|
-
// until PG itself exits (causing a 60s timeout even though PG is ready).
|
|
765
|
-
// Use exec() on Windows (matches process-manager.ts approach) which runs
|
|
766
|
-
// through the shell and doesn't hold pipes open. On Unix, use -w (wait mode).
|
|
767
|
-
try {
|
|
768
|
-
if (isWindows()) {
|
|
769
|
-
const cmd = `"${pgCtl}" start -D "${pgDataDir}" -l "${pgLogFile}" -o "-p ${backendPort} -h 127.0.0.1"`
|
|
770
|
-
await execAsync(cmd, {
|
|
771
|
-
env: { ...process.env, ...pgSpawnEnv },
|
|
772
|
-
timeout: 30000,
|
|
773
|
-
})
|
|
774
|
-
} else {
|
|
775
|
-
const pgCtlArgs = [
|
|
776
|
-
'start',
|
|
777
|
-
'-D',
|
|
778
|
-
pgDataDir,
|
|
779
|
-
'-l',
|
|
780
|
-
pgLogFile,
|
|
781
|
-
'-o',
|
|
782
|
-
`-p ${backendPort} -h 127.0.0.1`,
|
|
783
|
-
'-w',
|
|
784
|
-
]
|
|
785
|
-
await spawnAsync(pgCtl, pgCtlArgs, {
|
|
786
|
-
env: pgSpawnEnv,
|
|
787
|
-
timeout: 60000,
|
|
788
|
-
})
|
|
789
|
-
}
|
|
790
|
-
} catch (pgError) {
|
|
791
|
-
// Read PostgreSQL log for debugging
|
|
792
|
-
let pgLog = ''
|
|
793
|
-
try {
|
|
794
|
-
pgLog = await readFile(pgLogFile, 'utf8')
|
|
795
|
-
} catch {
|
|
796
|
-
pgLog = '(no log available)'
|
|
797
|
-
}
|
|
798
|
-
throw new Error(
|
|
799
|
-
`PostgreSQL backend failed to start: ${pgError instanceof Error ? pgError.message : pgError}\n` +
|
|
800
|
-
`PostgreSQL log:\n${pgLog.slice(-2000)}`, // Last 2KB of log
|
|
801
|
-
)
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
pgStarted = true
|
|
806
|
-
logDebug(`PostgreSQL started on port ${backendPort}`)
|
|
807
|
-
|
|
808
|
-
// 2. Wait for PostgreSQL to be ready
|
|
809
|
-
onProgress?.({ stage: 'starting', message: 'Waiting for PostgreSQL...' })
|
|
810
|
-
const pgReady = await waitForPort(backendPort, 30000)
|
|
811
|
-
if (!pgReady) {
|
|
812
|
-
throw new Error('PostgreSQL failed to start within timeout')
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// 3. Create ferretdb database and extension (first start)
|
|
816
|
-
// For v1 without psql, database was already created pre-start via postgres --single
|
|
817
|
-
if (psql) {
|
|
818
|
-
onProgress?.({
|
|
819
|
-
stage: 'starting',
|
|
820
|
-
message: 'Initializing FerretDB database...',
|
|
821
|
-
})
|
|
822
|
-
try {
|
|
823
|
-
// Create ferretdb database if it doesn't exist
|
|
824
|
-
await spawnAsync(
|
|
825
|
-
psql,
|
|
826
|
-
[
|
|
827
|
-
'-h',
|
|
828
|
-
'127.0.0.1',
|
|
829
|
-
'-p',
|
|
830
|
-
String(backendPort),
|
|
831
|
-
'-U',
|
|
832
|
-
'postgres',
|
|
833
|
-
'-c',
|
|
834
|
-
"CREATE DATABASE ferretdb WITH ENCODING 'UTF8';",
|
|
835
|
-
],
|
|
836
|
-
{ env: pgSpawnEnv, timeout: 30000 },
|
|
837
|
-
).catch(() => {
|
|
838
|
-
// Ignore error if database already exists (error code 42P04)
|
|
839
|
-
})
|
|
840
|
-
|
|
841
|
-
// v2 only: Create DocumentDB extension
|
|
842
|
-
// v1 uses plain PostgreSQL without DocumentDB
|
|
843
|
-
if (!v1) {
|
|
844
|
-
await spawnAsync(
|
|
845
|
-
psql,
|
|
846
|
-
[
|
|
847
|
-
'-h',
|
|
848
|
-
'127.0.0.1',
|
|
849
|
-
'-p',
|
|
850
|
-
String(backendPort),
|
|
851
|
-
'-U',
|
|
852
|
-
'postgres',
|
|
853
|
-
'-d',
|
|
854
|
-
'ferretdb',
|
|
855
|
-
'-c',
|
|
856
|
-
'CREATE EXTENSION IF NOT EXISTS documentdb CASCADE;',
|
|
857
|
-
],
|
|
858
|
-
{ env: pgSpawnEnv, timeout: 30000 },
|
|
859
|
-
).catch((error) => {
|
|
860
|
-
logWarning(`Failed to create documentdb extension: ${error}`)
|
|
861
|
-
// Continue anyway - extension might already exist
|
|
862
|
-
})
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
logDebug('FerretDB database initialized')
|
|
866
|
-
} catch (error) {
|
|
867
|
-
logDebug(`Database initialization warning: ${error}`)
|
|
868
|
-
// Continue - might already be initialized
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// 4. Start FerretDB proxy
|
|
873
|
-
onProgress?.({ stage: 'starting', message: 'Starting FerretDB proxy...' })
|
|
874
|
-
|
|
875
|
-
// Disable authentication for local development (similar to PostgreSQL trust auth)
|
|
876
|
-
// FerretDB 2.x has auth enabled by default, but for local dev we disable it
|
|
877
|
-
// Use a unique debug port to avoid conflicts when running multiple FerretDB containers
|
|
878
|
-
const FERRETDB_DEBUG_PORT_OFFSET = 10000
|
|
879
|
-
const FERRETDB_DEBUG_PORT_MAX_ATTEMPTS = 10
|
|
880
|
-
let debugPort = port + FERRETDB_DEBUG_PORT_OFFSET
|
|
881
|
-
|
|
882
|
-
// Probe for an available debug port, incrementing if the computed one is occupied
|
|
883
|
-
let debugPortFound = false
|
|
884
|
-
for (
|
|
885
|
-
let attempt = 0;
|
|
886
|
-
attempt < FERRETDB_DEBUG_PORT_MAX_ATTEMPTS;
|
|
887
|
-
attempt++
|
|
888
|
-
) {
|
|
889
|
-
const candidatePort = debugPort + attempt
|
|
890
|
-
if (await isPortAvailable(candidatePort)) {
|
|
891
|
-
debugPort = candidatePort
|
|
892
|
-
debugPortFound = true
|
|
893
|
-
break
|
|
894
|
-
}
|
|
895
|
-
logDebug(`Debug port ${candidatePort} is occupied, trying next...`)
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (!debugPortFound) {
|
|
899
|
-
logWarning(
|
|
900
|
-
`Could not find available debug port in range ${debugPort}-${debugPort + FERRETDB_DEBUG_PORT_MAX_ATTEMPTS - 1}, using computed port ${port + FERRETDB_DEBUG_PORT_OFFSET} anyway`,
|
|
901
|
-
)
|
|
902
|
-
debugPort = port + FERRETDB_DEBUG_PORT_OFFSET
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
logDebug(`Using debug port ${debugPort} for FerretDB HTTP debug handler`)
|
|
906
|
-
|
|
907
|
-
// v1 uses plain PostgreSQL without TLS configured, so sslmode=disable is required
|
|
908
|
-
// v2 uses postgresql-documentdb which handles SSL negotiation internally
|
|
909
|
-
const pgUrl = isV1(version)
|
|
910
|
-
? `postgres://postgres@127.0.0.1:${backendPort}/ferretdb?sslmode=disable`
|
|
911
|
-
: `postgres://postgres@127.0.0.1:${backendPort}/ferretdb`
|
|
912
|
-
|
|
913
|
-
const ferretArgs = [
|
|
914
|
-
'--listen-addr',
|
|
915
|
-
`127.0.0.1:${port}`,
|
|
916
|
-
'--postgresql-url',
|
|
917
|
-
pgUrl,
|
|
918
|
-
'--state-dir',
|
|
919
|
-
containerDir,
|
|
920
|
-
// v2 requires --no-auth to disable SCRAM authentication
|
|
921
|
-
// v1 has auth disabled by default (flag doesn't exist)
|
|
922
|
-
...(isV1(version) ? [] : ['--no-auth']),
|
|
923
|
-
'--debug-addr',
|
|
924
|
-
`127.0.0.1:${debugPort}`,
|
|
925
|
-
]
|
|
926
|
-
|
|
927
|
-
logDebug(`Starting FerretDB with args: ${ferretArgs.join(' ')}`)
|
|
928
|
-
|
|
929
|
-
const spawnOpts: SpawnOptions = {
|
|
930
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
931
|
-
detached: true,
|
|
932
|
-
// Run FerretDB in the container directory so telemetry.json/state.json
|
|
933
|
-
// are written there instead of polluting the user's cwd
|
|
934
|
-
cwd: containerDir,
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const proc = spawn(ferretdbBinary, ferretArgs, spawnOpts)
|
|
938
|
-
|
|
939
|
-
// Log output
|
|
940
|
-
let stderrOutput = ''
|
|
941
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
942
|
-
logDebug(`ferretdb stdout: ${data.toString()}`)
|
|
943
|
-
})
|
|
944
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
945
|
-
stderrOutput += data.toString()
|
|
946
|
-
logDebug(`ferretdb stderr: ${data.toString()}`)
|
|
947
|
-
})
|
|
948
|
-
|
|
949
|
-
proc.unref()
|
|
950
|
-
|
|
951
|
-
// Write PID file
|
|
952
|
-
if (proc.pid) {
|
|
953
|
-
await writeFile(ferretPidFile, String(proc.pid))
|
|
954
|
-
ferretStarted = true
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// 5. Wait for FerretDB to be ready
|
|
958
|
-
const ferretReady = await waitForPort(port, 30000)
|
|
959
|
-
if (!ferretReady) {
|
|
960
|
-
throw new Error(
|
|
961
|
-
`FerretDB failed to start within timeout. Stderr: ${stderrOutput}`,
|
|
962
|
-
)
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
logDebug(`FerretDB started on port ${port}`)
|
|
966
|
-
|
|
967
|
-
// Persist the allocated backend port if newly allocated
|
|
968
|
-
if (!existingBackendPort && backendPort) {
|
|
969
|
-
await containerManager.updateConfig(name, { backendPort })
|
|
970
|
-
logDebug(`Persisted backend port ${backendPort} to container config`)
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
return {
|
|
974
|
-
port,
|
|
975
|
-
connectionString: this.getConnectionString(container),
|
|
976
|
-
}
|
|
977
|
-
} catch (error) {
|
|
978
|
-
// Rollback: stop any started processes
|
|
979
|
-
if (ferretStarted) {
|
|
980
|
-
await this.stopFerretDBProcess(containerDir).catch(() => {})
|
|
981
|
-
}
|
|
982
|
-
if (pgStarted) {
|
|
983
|
-
await this.stopPostgreSQLProcess(pgCtl, pgDataDir, pgSpawnEnv).catch(
|
|
984
|
-
() => {},
|
|
985
|
-
)
|
|
986
|
-
}
|
|
987
|
-
throw error
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* Stop FerretDB (reverse order: FerretDB first, then PostgreSQL)
|
|
993
|
-
*/
|
|
994
|
-
async stop(container: ContainerConfig): Promise<void> {
|
|
995
|
-
const { name, version, backendVersion } = container
|
|
996
|
-
const { platform, arch } = this.getPlatformInfo()
|
|
997
|
-
const v1 = isV1(version)
|
|
998
|
-
|
|
999
|
-
const effectiveBackendVersion = v1
|
|
1000
|
-
? backendVersion || DEFAULT_V1_POSTGRESQL_VERSION
|
|
1001
|
-
: backendVersion || DEFAULT_DOCUMENTDB_VERSION
|
|
1002
|
-
|
|
1003
|
-
const { backendPath: documentdbPath, pgSpawnEnv } = this.getBackendPaths(
|
|
1004
|
-
version,
|
|
1005
|
-
effectiveBackendVersion,
|
|
1006
|
-
platform,
|
|
1007
|
-
arch,
|
|
1008
|
-
)
|
|
1009
|
-
|
|
1010
|
-
const ext = platformService.getExecutableExtension()
|
|
1011
|
-
const pgCtl = join(documentdbPath, 'bin', `pg_ctl${ext}`)
|
|
1012
|
-
|
|
1013
|
-
const containerDir = paths.getContainerPath(name, { engine: ENGINE })
|
|
1014
|
-
const pgDataDir = join(containerDir, 'pg_data')
|
|
1015
|
-
|
|
1016
|
-
logDebug(`Stopping FerretDB container "${name}"`)
|
|
1017
|
-
|
|
1018
|
-
// 1. Stop FerretDB proxy
|
|
1019
|
-
await this.stopFerretDBProcess(containerDir)
|
|
1020
|
-
|
|
1021
|
-
// 2. Stop PostgreSQL
|
|
1022
|
-
if (existsSync(pgCtl)) {
|
|
1023
|
-
await this.stopPostgreSQLProcess(pgCtl, pgDataDir, pgSpawnEnv)
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// Kill pgweb if running for this container
|
|
1027
|
-
await this.stopPgweb(name)
|
|
1028
|
-
|
|
1029
|
-
logDebug('FerretDB stopped')
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
/**
|
|
1033
|
-
* Fix hardcoded Homebrew dylib paths in extension libraries.
|
|
1034
|
-
*
|
|
1035
|
-
* The x64 darwin build of postgresql-documentdb has extensions whose dylib
|
|
1036
|
-
* load commands reference absolute Homebrew paths (e.g.
|
|
1037
|
-
* /usr/local/opt/mongo-c-driver/lib/libbson2.2.dylib). When these paths
|
|
1038
|
-
* don't exist on the target machine, the extension fails to load.
|
|
1039
|
-
*
|
|
1040
|
-
* This method scans extension dylibs with `otool -L`, finds missing
|
|
1041
|
-
* dependencies, searches our bundle for matching libraries, and creates
|
|
1042
|
-
* symlinks at the expected Homebrew paths.
|
|
1043
|
-
*/
|
|
1044
|
-
private async fixDylibDependencies(documentdbPath: string): Promise<void> {
|
|
1045
|
-
const libDir = join(documentdbPath, 'lib')
|
|
1046
|
-
const pkgLibDir = join(libDir, 'postgresql')
|
|
1047
|
-
|
|
1048
|
-
if (!existsSync(pkgLibDir)) return
|
|
1049
|
-
|
|
1050
|
-
// Collect all .dylib files in our bundle's lib/ directory
|
|
1051
|
-
const bundledLibNames = new Set<string>()
|
|
1052
|
-
const bundledLibPaths = new Map<string, string>()
|
|
1053
|
-
try {
|
|
1054
|
-
const libFiles = await readdir(libDir)
|
|
1055
|
-
for (const f of libFiles) {
|
|
1056
|
-
if (f.endsWith('.dylib')) {
|
|
1057
|
-
bundledLibNames.add(f)
|
|
1058
|
-
bundledLibPaths.set(f, join(libDir, f))
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
} catch {
|
|
1062
|
-
return
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// Scan extension dylibs for missing dependencies
|
|
1066
|
-
let extFiles: string[]
|
|
1067
|
-
try {
|
|
1068
|
-
extFiles = (await readdir(pkgLibDir)).filter((f) => f.endsWith('.dylib'))
|
|
1069
|
-
} catch {
|
|
1070
|
-
return
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
for (const extFile of extFiles) {
|
|
1074
|
-
const extPath = join(pkgLibDir, extFile)
|
|
1075
|
-
try {
|
|
1076
|
-
const { stdout } = await execAsync(`otool -L "${extPath}"`, {
|
|
1077
|
-
timeout: 5000,
|
|
1078
|
-
})
|
|
1079
|
-
|
|
1080
|
-
for (const line of stdout.split('\n')) {
|
|
1081
|
-
const match = line.trim().match(/^(\/[^\s]+\.dylib)\s/)
|
|
1082
|
-
if (!match) continue
|
|
1083
|
-
const depPath = match[1]
|
|
1084
|
-
|
|
1085
|
-
// Skip system libs, @-prefixed paths, and paths in our bundle
|
|
1086
|
-
if (depPath.startsWith('/usr/lib/')) continue
|
|
1087
|
-
if (depPath.startsWith('/System/')) continue
|
|
1088
|
-
if (depPath.startsWith('@')) continue
|
|
1089
|
-
if (depPath.includes(documentdbPath)) continue
|
|
1090
|
-
|
|
1091
|
-
if (!existsSync(depPath)) {
|
|
1092
|
-
const depName = basename(depPath)
|
|
1093
|
-
|
|
1094
|
-
// Check if we have this exact library in our bundle
|
|
1095
|
-
if (bundledLibPaths.has(depName)) {
|
|
1096
|
-
try {
|
|
1097
|
-
await mkdir(dirname(depPath), { recursive: true })
|
|
1098
|
-
} catch {
|
|
1099
|
-
logDebug(
|
|
1100
|
-
`Cannot create directory for dylib dep: ${dirname(depPath)} (skipping)`,
|
|
1101
|
-
)
|
|
1102
|
-
continue
|
|
1103
|
-
}
|
|
1104
|
-
try {
|
|
1105
|
-
await symlink(bundledLibPaths.get(depName)!, depPath)
|
|
1106
|
-
logDebug(
|
|
1107
|
-
`Fixed dylib dep: ${depPath} -> ${bundledLibPaths.get(depName)}`,
|
|
1108
|
-
)
|
|
1109
|
-
} catch {
|
|
1110
|
-
// Symlink may already exist from a parallel fix
|
|
1111
|
-
}
|
|
1112
|
-
} else {
|
|
1113
|
-
logDebug(
|
|
1114
|
-
`Missing dylib dependency: ${depPath} (not found in bundle)`,
|
|
1115
|
-
)
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
} catch {
|
|
1120
|
-
logDebug(`Could not scan dylib deps for ${extFile}`)
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* Stop FerretDB proxy process
|
|
1127
|
-
*/
|
|
1128
|
-
private async stopFerretDBProcess(containerDir: string): Promise<void> {
|
|
1129
|
-
const pidFile = join(containerDir, 'ferretdb.pid')
|
|
1130
|
-
|
|
1131
|
-
if (existsSync(pidFile)) {
|
|
1132
|
-
let pid = NaN
|
|
1133
|
-
try {
|
|
1134
|
-
const pidContent = await readFile(pidFile, 'utf8')
|
|
1135
|
-
pid = parseInt(pidContent.trim(), 10)
|
|
1136
|
-
} catch {
|
|
1137
|
-
// PID file unreadable — clean it up below
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
if (!isNaN(pid) && platformService.isProcessRunning(pid)) {
|
|
1141
|
-
logDebug(`Killing FerretDB process ${pid}`)
|
|
1142
|
-
|
|
1143
|
-
// On Windows, taskkill without /F sends WM_CLOSE which console/server
|
|
1144
|
-
// processes ignore, causing an error. Use force kill directly.
|
|
1145
|
-
if (isWindows()) {
|
|
1146
|
-
try {
|
|
1147
|
-
await platformService.terminateProcess(pid, true)
|
|
1148
|
-
} catch {
|
|
1149
|
-
logDebug(`Force kill of FerretDB process ${pid} failed`)
|
|
1150
|
-
}
|
|
1151
|
-
} else {
|
|
1152
|
-
// Unix: try graceful SIGTERM first, then SIGKILL
|
|
1153
|
-
try {
|
|
1154
|
-
await platformService.terminateProcess(pid, false)
|
|
1155
|
-
} catch {
|
|
1156
|
-
// Graceful termination failed — force kill below
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// Poll until process exits or timeout (10 seconds)
|
|
1160
|
-
const maxWaitMs = 10000
|
|
1161
|
-
const pollIntervalMs = 200
|
|
1162
|
-
const startTime = Date.now()
|
|
1163
|
-
|
|
1164
|
-
while (Date.now() - startTime < maxWaitMs) {
|
|
1165
|
-
if (!platformService.isProcessRunning(pid)) {
|
|
1166
|
-
logDebug(`FerretDB process ${pid} terminated gracefully`)
|
|
1167
|
-
break
|
|
1168
|
-
}
|
|
1169
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs))
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// Force kill if still running after timeout
|
|
1173
|
-
if (platformService.isProcessRunning(pid)) {
|
|
1174
|
-
logWarning(`Graceful termination timed out, force killing ${pid}`)
|
|
1175
|
-
try {
|
|
1176
|
-
await platformService.terminateProcess(pid, true)
|
|
1177
|
-
} catch {
|
|
1178
|
-
logDebug(`Force kill of FerretDB process ${pid} failed`)
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// Wait briefly for process to fully exit after force kill
|
|
1184
|
-
const exitWaitMs = isWindows() ? 3000 : 1000
|
|
1185
|
-
const pollMs = 100
|
|
1186
|
-
const exitStart = Date.now()
|
|
1187
|
-
while (Date.now() - exitStart < exitWaitMs) {
|
|
1188
|
-
if (!platformService.isProcessRunning(pid)) break
|
|
1189
|
-
await new Promise((resolve) => setTimeout(resolve, pollMs))
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// Always clean up PID file
|
|
1194
|
-
await unlink(pidFile).catch(() => {})
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Stop PostgreSQL process
|
|
1200
|
-
*/
|
|
1201
|
-
private async stopPostgreSQLProcess(
|
|
1202
|
-
pgCtl: string,
|
|
1203
|
-
pgDataDir: string,
|
|
1204
|
-
spawnEnv?: Record<string, string>,
|
|
1205
|
-
): Promise<void> {
|
|
1206
|
-
if (isWindows()) {
|
|
1207
|
-
// On Windows, use exec() instead of spawnAsync() to avoid pipe-related
|
|
1208
|
-
// hangs (same issue as pg_ctl start -w). pg_ctl stop -w can block when
|
|
1209
|
-
// stdout/stderr pipes prevent the child process from exiting cleanly.
|
|
1210
|
-
try {
|
|
1211
|
-
await execAsync(`"${pgCtl}" stop -D "${pgDataDir}" -m fast -w`, {
|
|
1212
|
-
timeout: 30000,
|
|
1213
|
-
env: spawnEnv ? { ...process.env, ...spawnEnv } : undefined,
|
|
1214
|
-
})
|
|
1215
|
-
logDebug('PostgreSQL stopped')
|
|
1216
|
-
} catch (error) {
|
|
1217
|
-
logDebug(`pg_ctl stop error: ${error}`)
|
|
1218
|
-
try {
|
|
1219
|
-
await execAsync(`"${pgCtl}" stop -D "${pgDataDir}" -m immediate -w`, {
|
|
1220
|
-
timeout: 15000,
|
|
1221
|
-
env: spawnEnv ? { ...process.env, ...spawnEnv } : undefined,
|
|
1222
|
-
})
|
|
1223
|
-
} catch {
|
|
1224
|
-
logWarning('Failed to stop PostgreSQL gracefully')
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
} else {
|
|
1228
|
-
try {
|
|
1229
|
-
await spawnAsync(pgCtl, ['stop', '-D', pgDataDir, '-m', 'fast', '-w'], {
|
|
1230
|
-
env: spawnEnv,
|
|
1231
|
-
timeout: 30000,
|
|
1232
|
-
})
|
|
1233
|
-
logDebug('PostgreSQL stopped')
|
|
1234
|
-
} catch (error) {
|
|
1235
|
-
logDebug(`pg_ctl stop error: ${error}`)
|
|
1236
|
-
try {
|
|
1237
|
-
await spawnAsync(
|
|
1238
|
-
pgCtl,
|
|
1239
|
-
['stop', '-D', pgDataDir, '-m', 'immediate', '-w'],
|
|
1240
|
-
{ env: spawnEnv, timeout: 15000 },
|
|
1241
|
-
)
|
|
1242
|
-
} catch {
|
|
1243
|
-
logWarning('Failed to stop PostgreSQL gracefully')
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
// Get FerretDB server status
|
|
1250
|
-
async status(container: ContainerConfig): Promise<StatusResult> {
|
|
1251
|
-
const { name, port } = container
|
|
1252
|
-
const containerDir = paths.getContainerPath(name, { engine: ENGINE })
|
|
1253
|
-
const pidFile = join(containerDir, 'ferretdb.pid')
|
|
1254
|
-
|
|
1255
|
-
// Check if FerretDB is responding
|
|
1256
|
-
const isOpen = await new Promise<boolean>((resolve) => {
|
|
1257
|
-
const socket = new net.Socket()
|
|
1258
|
-
socket.setTimeout(1000)
|
|
1259
|
-
socket.once('connect', () => {
|
|
1260
|
-
socket.destroy()
|
|
1261
|
-
resolve(true)
|
|
1262
|
-
})
|
|
1263
|
-
socket.once('error', () => {
|
|
1264
|
-
socket.destroy()
|
|
1265
|
-
resolve(false)
|
|
1266
|
-
})
|
|
1267
|
-
socket.once('timeout', () => {
|
|
1268
|
-
socket.destroy()
|
|
1269
|
-
resolve(false)
|
|
1270
|
-
})
|
|
1271
|
-
socket.connect(port, '127.0.0.1')
|
|
1272
|
-
})
|
|
1273
|
-
|
|
1274
|
-
if (isOpen) {
|
|
1275
|
-
return { running: true, message: 'FerretDB is running' }
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
// Check PID file
|
|
1279
|
-
if (existsSync(pidFile)) {
|
|
1280
|
-
try {
|
|
1281
|
-
const content = await readFile(pidFile, 'utf8')
|
|
1282
|
-
const pid = parseInt(content.trim(), 10)
|
|
1283
|
-
if (!isNaN(pid) && pid > 0 && platformService.isProcessRunning(pid)) {
|
|
1284
|
-
return {
|
|
1285
|
-
running: true,
|
|
1286
|
-
message: `FerretDB is running (PID: ${pid})`,
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
} catch {
|
|
1290
|
-
// Ignore
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
return { running: false, message: 'FerretDB is not running' }
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// Detect backup format
|
|
1298
|
-
async detectBackupFormat(filePath: string): Promise<BackupFormat> {
|
|
1299
|
-
return detectBackupFormatImpl(filePath)
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
// Restore a backup
|
|
1303
|
-
async restore(
|
|
1304
|
-
container: ContainerConfig,
|
|
1305
|
-
backupPath: string,
|
|
1306
|
-
options: Record<string, unknown> = {},
|
|
1307
|
-
): Promise<RestoreResult> {
|
|
1308
|
-
const { backendPort } = container
|
|
1309
|
-
const database = (options.database as string) || 'ferretdb'
|
|
1310
|
-
|
|
1311
|
-
if (!backendPort) {
|
|
1312
|
-
throw new Error('Backend port not set - start the container first')
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
// Validate database name before restore (defense-in-depth)
|
|
1316
|
-
assertValidDatabaseName(database)
|
|
1317
|
-
|
|
1318
|
-
const result = await restoreBackup(container, backupPath, {
|
|
1319
|
-
database,
|
|
1320
|
-
drop: options.drop !== false,
|
|
1321
|
-
})
|
|
1322
|
-
|
|
1323
|
-
// Restart FerretDB proxy so it picks up the restored data.
|
|
1324
|
-
// pg_restore writes directly to PostgreSQL, but FerretDB's proxy
|
|
1325
|
-
// caches schema/collection metadata in memory and won't see
|
|
1326
|
-
// the restored collections until restarted.
|
|
1327
|
-
const containerDir = paths.getContainerPath(container.name, {
|
|
1328
|
-
engine: ENGINE,
|
|
1329
|
-
})
|
|
1330
|
-
try {
|
|
1331
|
-
await this.stopFerretDBProcess(containerDir)
|
|
1332
|
-
// start() detects PG is already running and only launches the proxy
|
|
1333
|
-
await this.start(container)
|
|
1334
|
-
} catch (error) {
|
|
1335
|
-
const err = error as Error
|
|
1336
|
-
logWarning(
|
|
1337
|
-
`Failed to restart FerretDB proxy after restore: ${err.message}`,
|
|
1338
|
-
)
|
|
1339
|
-
// Retry once — transient issues (port race, slow PG) can resolve on second attempt
|
|
1340
|
-
try {
|
|
1341
|
-
await this.stopFerretDBProcess(containerDir).catch(() => {})
|
|
1342
|
-
await this.start(container)
|
|
1343
|
-
} catch {
|
|
1344
|
-
throw new Error(
|
|
1345
|
-
`Restore succeeded but FerretDB proxy failed to restart. ` +
|
|
1346
|
-
`Data is safely in PostgreSQL. Run 'spindb start ${container.name}' to restart manually. ` +
|
|
1347
|
-
`Original error: ${err.message}`,
|
|
1348
|
-
)
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
return result
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
// Get connection string (MongoDB-compatible)
|
|
1356
|
-
getConnectionString(container: ContainerConfig, database?: string): string {
|
|
1357
|
-
const { port } = container
|
|
1358
|
-
const db = database || container.database || 'test'
|
|
1359
|
-
// No authentication required - FerretDB runs with --no-auth for local dev
|
|
1360
|
-
return `mongodb://127.0.0.1:${port}/${db}`
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// Get PostgreSQL backend connection string (for debugging)
|
|
1364
|
-
getBackendConnectionString(container: ContainerConfig): string {
|
|
1365
|
-
const { backendPort } = container
|
|
1366
|
-
if (!backendPort) {
|
|
1367
|
-
throw new Error(
|
|
1368
|
-
'Backend port not available - start the container first to allocate a port',
|
|
1369
|
-
)
|
|
1370
|
-
}
|
|
1371
|
-
return `postgresql://postgres@127.0.0.1:${backendPort}/ferretdb`
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
/**
|
|
1375
|
-
* Get the path to mongosh (uses MongoDB's mongosh)
|
|
1376
|
-
* FerretDB is MongoDB-compatible, so it uses the same shell
|
|
1377
|
-
*/
|
|
1378
|
-
override async getMongoshPath(): Promise<string> {
|
|
1379
|
-
const cached = await configManager.getBinaryPath('mongosh')
|
|
1380
|
-
if (cached && existsSync(cached)) return cached
|
|
1381
|
-
|
|
1382
|
-
// Try to find in PATH as fallback
|
|
1383
|
-
const detected = await platformService.findToolPath('mongosh')
|
|
1384
|
-
if (detected) {
|
|
1385
|
-
await configManager.setBinaryPath('mongosh', detected, 'system')
|
|
1386
|
-
return detected
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
throw new Error(
|
|
1390
|
-
'mongosh not found. To connect to FerretDB, install mongosh:\n' +
|
|
1391
|
-
' Download from: https://www.mongodb.com/try/download/shell\n' +
|
|
1392
|
-
' Or download MongoDB binaries: spindb engines download mongodb <version>',
|
|
1393
|
-
)
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// Open mongosh interactive shell
|
|
1397
|
-
async connect(container: ContainerConfig, database?: string): Promise<void> {
|
|
1398
|
-
const { port } = container
|
|
1399
|
-
const db = database || 'test'
|
|
1400
|
-
|
|
1401
|
-
const mongosh = await this.getMongoshPath()
|
|
1402
|
-
|
|
1403
|
-
const spawnOptions: SpawnOptions = {
|
|
1404
|
-
stdio: 'inherit',
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
return new Promise((resolve, reject) => {
|
|
1408
|
-
const proc = spawn(
|
|
1409
|
-
mongosh,
|
|
1410
|
-
['--host', '127.0.0.1', '--port', String(port), db],
|
|
1411
|
-
spawnOptions,
|
|
1412
|
-
)
|
|
1413
|
-
|
|
1414
|
-
proc.on('error', reject)
|
|
1415
|
-
proc.on('close', () => resolve())
|
|
1416
|
-
})
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
/**
|
|
1420
|
-
* Create a new database
|
|
1421
|
-
* FerretDB/MongoDB creates databases implicitly when you first write to them.
|
|
1422
|
-
* To force immediate creation, we create a temporary collection and drop it.
|
|
1423
|
-
* This leaves the database visible in tools without any marker clutter.
|
|
1424
|
-
*/
|
|
1425
|
-
async createDatabase(
|
|
1426
|
-
container: ContainerConfig,
|
|
1427
|
-
database: string,
|
|
1428
|
-
): Promise<void> {
|
|
1429
|
-
assertValidDatabaseName(database)
|
|
1430
|
-
const { port } = container
|
|
1431
|
-
|
|
1432
|
-
try {
|
|
1433
|
-
const mongosh = await this.getMongoshPath()
|
|
1434
|
-
// Create a temp collection then immediately drop it to force database creation
|
|
1435
|
-
// without leaving any visible marker collections.
|
|
1436
|
-
// Pre-drop in case a previous run was interrupted and left a stale collection.
|
|
1437
|
-
// NOTE: Use db.getCollection() instead of db._spindb_init shorthand because
|
|
1438
|
-
// mongosh doesn't support shorthand notation for collection names starting with underscore.
|
|
1439
|
-
const script =
|
|
1440
|
-
'try { db.getCollection("_spindb_init").drop(); } catch(e) {} db.createCollection("_spindb_init"); db.getCollection("_spindb_init").drop();'
|
|
1441
|
-
const cmd = isWindows()
|
|
1442
|
-
? `"${mongosh}" --host 127.0.0.1 --port ${port} ${database} --eval "${script.replace(/"/g, '\\"')}"`
|
|
1443
|
-
: `"${mongosh}" --host 127.0.0.1 --port ${port} ${database} --eval '${script}'`
|
|
1444
|
-
|
|
1445
|
-
await execAsync(cmd, { timeout: 10000 })
|
|
1446
|
-
logDebug(`Database "${database}" created via temp collection`)
|
|
1447
|
-
} catch (error) {
|
|
1448
|
-
// Ignore errors - database may already exist or collection cleanup succeeded
|
|
1449
|
-
logDebug(`createDatabase result: ${error}`)
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
// Drop a database
|
|
1454
|
-
async dropDatabase(
|
|
1455
|
-
container: ContainerConfig,
|
|
1456
|
-
database: string,
|
|
1457
|
-
): Promise<void> {
|
|
1458
|
-
assertValidDatabaseName(database)
|
|
1459
|
-
const { port } = container
|
|
1460
|
-
|
|
1461
|
-
try {
|
|
1462
|
-
const mongosh = await this.getMongoshPath()
|
|
1463
|
-
const cmd = isWindows()
|
|
1464
|
-
? `"${mongosh}" --host 127.0.0.1 --port ${port} ${database} --eval "db.dropDatabase()"`
|
|
1465
|
-
: `"${mongosh}" --host 127.0.0.1 --port ${port} ${database} --eval 'db.dropDatabase()'`
|
|
1466
|
-
|
|
1467
|
-
await execAsync(cmd, { timeout: 10000 })
|
|
1468
|
-
} catch (error) {
|
|
1469
|
-
logDebug(`dropDatabase result: ${error}`)
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
// Get the size of the database in bytes
|
|
1474
|
-
async getDatabaseSize(container: ContainerConfig): Promise<number | null> {
|
|
1475
|
-
const { port, database } = container
|
|
1476
|
-
const db = database || 'test'
|
|
1477
|
-
assertValidDatabaseName(db)
|
|
1478
|
-
|
|
1479
|
-
try {
|
|
1480
|
-
const mongosh = await this.getMongoshPath()
|
|
1481
|
-
const script = 'JSON.stringify(db.stats())'
|
|
1482
|
-
const cmd = isWindows()
|
|
1483
|
-
? `"${mongosh}" --host 127.0.0.1 --port ${port} ${db} --quiet --eval "${script}"`
|
|
1484
|
-
: `"${mongosh}" --host 127.0.0.1 --port ${port} ${db} --quiet --eval '${script}'`
|
|
1485
|
-
|
|
1486
|
-
const { stdout } = await execAsync(cmd, { timeout: 10000 })
|
|
1487
|
-
|
|
1488
|
-
// Extract JSON from output
|
|
1489
|
-
const firstBrace = stdout.indexOf('{')
|
|
1490
|
-
const lastBrace = stdout.lastIndexOf('}')
|
|
1491
|
-
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
|
1492
|
-
const stats = JSON.parse(stdout.substring(firstBrace, lastBrace + 1))
|
|
1493
|
-
const dataSize = Number(stats?.dataSize)
|
|
1494
|
-
return Number.isFinite(dataSize) && dataSize > 0 ? dataSize : null
|
|
1495
|
-
}
|
|
1496
|
-
return null
|
|
1497
|
-
} catch {
|
|
1498
|
-
return null
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
// Create a dump from a remote database
|
|
1503
|
-
async dumpFromConnectionString(
|
|
1504
|
-
connectionString: string,
|
|
1505
|
-
outputPath: string,
|
|
1506
|
-
): Promise<DumpResult> {
|
|
1507
|
-
// Use mongodump if available
|
|
1508
|
-
const mongodump = await configManager.getBinaryPath('mongodump')
|
|
1509
|
-
if (!mongodump) {
|
|
1510
|
-
throw new Error(
|
|
1511
|
-
'mongodump not found. Download MongoDB binaries:\n' +
|
|
1512
|
-
' Run: spindb engines download mongodb <version>',
|
|
1513
|
-
)
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
const args = [
|
|
1517
|
-
'--uri',
|
|
1518
|
-
connectionString,
|
|
1519
|
-
'--archive=' + outputPath,
|
|
1520
|
-
'--gzip',
|
|
1521
|
-
]
|
|
1522
|
-
|
|
1523
|
-
const spawnOptions: SpawnOptions = {
|
|
1524
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
return new Promise((resolve, reject) => {
|
|
1528
|
-
const proc = spawn(mongodump, args, spawnOptions)
|
|
1529
|
-
|
|
1530
|
-
let stdout = ''
|
|
1531
|
-
let stderr = ''
|
|
1532
|
-
|
|
1533
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
1534
|
-
stdout += data.toString()
|
|
1535
|
-
})
|
|
1536
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1537
|
-
stderr += data.toString()
|
|
1538
|
-
})
|
|
1539
|
-
|
|
1540
|
-
proc.on('error', reject)
|
|
1541
|
-
|
|
1542
|
-
proc.on('close', (code) => {
|
|
1543
|
-
if (code === 0) {
|
|
1544
|
-
resolve({
|
|
1545
|
-
filePath: outputPath,
|
|
1546
|
-
stdout,
|
|
1547
|
-
stderr,
|
|
1548
|
-
code,
|
|
1549
|
-
})
|
|
1550
|
-
} else {
|
|
1551
|
-
reject(new Error(stderr || `mongodump exited with code ${code}`))
|
|
1552
|
-
}
|
|
1553
|
-
})
|
|
1554
|
-
})
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
// Create a backup
|
|
1558
|
-
async backup(
|
|
1559
|
-
container: ContainerConfig,
|
|
1560
|
-
outputPath: string,
|
|
1561
|
-
options: BackupOptions,
|
|
1562
|
-
): Promise<BackupResult> {
|
|
1563
|
-
return createBackup(container, outputPath, options)
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
// Run a JavaScript file or inline script against the database
|
|
1567
|
-
async runScript(
|
|
1568
|
-
container: ContainerConfig,
|
|
1569
|
-
options: { file?: string; sql?: string; database?: string },
|
|
1570
|
-
): Promise<void> {
|
|
1571
|
-
const { port } = container
|
|
1572
|
-
const db = options.database || container.database || 'test'
|
|
1573
|
-
|
|
1574
|
-
const mongosh = await this.getMongoshPath()
|
|
1575
|
-
|
|
1576
|
-
if (options.file) {
|
|
1577
|
-
const spawnOptions: SpawnOptions = {
|
|
1578
|
-
stdio: 'inherit',
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
return new Promise((resolve, reject) => {
|
|
1582
|
-
const proc = spawn(
|
|
1583
|
-
mongosh,
|
|
1584
|
-
[
|
|
1585
|
-
'--host',
|
|
1586
|
-
'127.0.0.1',
|
|
1587
|
-
'--port',
|
|
1588
|
-
String(port),
|
|
1589
|
-
db,
|
|
1590
|
-
'--file',
|
|
1591
|
-
options.file!,
|
|
1592
|
-
],
|
|
1593
|
-
spawnOptions,
|
|
1594
|
-
)
|
|
1595
|
-
|
|
1596
|
-
proc.on('error', reject)
|
|
1597
|
-
proc.on('close', (code) => {
|
|
1598
|
-
if (code === 0) {
|
|
1599
|
-
resolve()
|
|
1600
|
-
} else {
|
|
1601
|
-
reject(new Error(`mongosh exited with code ${code}`))
|
|
1602
|
-
}
|
|
1603
|
-
})
|
|
1604
|
-
})
|
|
1605
|
-
} else if (options.sql) {
|
|
1606
|
-
// sql field is actually JS for MongoDB-compatible databases
|
|
1607
|
-
const script = options.sql
|
|
1608
|
-
|
|
1609
|
-
return new Promise((resolve, reject) => {
|
|
1610
|
-
const proc = spawn(
|
|
1611
|
-
mongosh,
|
|
1612
|
-
['--host', '127.0.0.1', '--port', String(port), db, '--eval', script],
|
|
1613
|
-
{ stdio: ['pipe', 'pipe', 'pipe'] },
|
|
1614
|
-
)
|
|
1615
|
-
|
|
1616
|
-
let stdout = ''
|
|
1617
|
-
let stderr = ''
|
|
1618
|
-
|
|
1619
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
1620
|
-
stdout += data.toString()
|
|
1621
|
-
})
|
|
1622
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1623
|
-
stderr += data.toString()
|
|
1624
|
-
})
|
|
1625
|
-
|
|
1626
|
-
// 60 second timeout
|
|
1627
|
-
const timeout = setTimeout(() => {
|
|
1628
|
-
proc.kill('SIGTERM')
|
|
1629
|
-
reject(new Error('mongosh timed out after 60 seconds'))
|
|
1630
|
-
}, 60000)
|
|
1631
|
-
|
|
1632
|
-
proc.on('error', (err) => {
|
|
1633
|
-
clearTimeout(timeout)
|
|
1634
|
-
reject(err)
|
|
1635
|
-
})
|
|
1636
|
-
|
|
1637
|
-
proc.on('close', (code) => {
|
|
1638
|
-
clearTimeout(timeout)
|
|
1639
|
-
if (stdout) process.stdout.write(stdout)
|
|
1640
|
-
if (stderr) process.stderr.write(stderr)
|
|
1641
|
-
if (code === 0) {
|
|
1642
|
-
resolve()
|
|
1643
|
-
} else {
|
|
1644
|
-
reject(new Error(`mongosh exited with code ${code}`))
|
|
1645
|
-
}
|
|
1646
|
-
})
|
|
1647
|
-
})
|
|
1648
|
-
} else {
|
|
1649
|
-
throw new Error('Either file or sql option must be provided')
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
/**
|
|
1654
|
-
* Execute a query and return structured results
|
|
1655
|
-
* FerretDB uses MongoDB JavaScript syntax
|
|
1656
|
-
*
|
|
1657
|
-
* Examples:
|
|
1658
|
-
* db.users.find({active: true})
|
|
1659
|
-
* db.orders.countDocuments()
|
|
1660
|
-
*/
|
|
1661
|
-
async executeQuery(
|
|
1662
|
-
container: ContainerConfig,
|
|
1663
|
-
query: string,
|
|
1664
|
-
options?: QueryOptions,
|
|
1665
|
-
): Promise<QueryResult> {
|
|
1666
|
-
const { port } = container
|
|
1667
|
-
const db = options?.database || container.database || 'test'
|
|
1668
|
-
|
|
1669
|
-
const mongosh = await this.getMongoshPath()
|
|
1670
|
-
|
|
1671
|
-
// Normalize query - only prepend "db." for collection operations
|
|
1672
|
-
// Collection operations match pattern: identifier.method(...) e.g., "users.find({})"
|
|
1673
|
-
// Non-collection queries (show dbs, arbitrary JS) are rejected with clear error
|
|
1674
|
-
let normalizedQuery = query.trim()
|
|
1675
|
-
if (!normalizedQuery.startsWith('db.')) {
|
|
1676
|
-
// Check if it looks like a collection operation: identifier.method(
|
|
1677
|
-
const collectionOpPattern =
|
|
1678
|
-
/^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*\s*\(/
|
|
1679
|
-
if (collectionOpPattern.test(normalizedQuery)) {
|
|
1680
|
-
normalizedQuery = `db.${normalizedQuery}`
|
|
1681
|
-
} else {
|
|
1682
|
-
throw new Error(
|
|
1683
|
-
'Invalid query format. Expected a collection operation like "users.find({})" or "db.users.find({})"\n' +
|
|
1684
|
-
'Shell commands like "show dbs" and "use dbname" are not supported in executeQuery.',
|
|
1685
|
-
)
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
// Wrap query in async IIFE to properly await cursor.toArray()
|
|
1690
|
-
// This prevents JSON.stringify from serializing a Promise
|
|
1691
|
-
const script = `(async () => { const res = ${normalizedQuery}; return JSON.stringify(res.toArray ? await res.toArray() : await Promise.resolve(res)); })()`
|
|
1692
|
-
|
|
1693
|
-
return new Promise((resolve, reject) => {
|
|
1694
|
-
const args = [
|
|
1695
|
-
'--host',
|
|
1696
|
-
'127.0.0.1',
|
|
1697
|
-
'--port',
|
|
1698
|
-
String(port),
|
|
1699
|
-
db,
|
|
1700
|
-
'--quiet',
|
|
1701
|
-
'--eval',
|
|
1702
|
-
script,
|
|
1703
|
-
]
|
|
1704
|
-
|
|
1705
|
-
const proc = spawn(mongosh, args, {
|
|
1706
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1707
|
-
})
|
|
1708
|
-
|
|
1709
|
-
let stdout = ''
|
|
1710
|
-
let stderr = ''
|
|
1711
|
-
|
|
1712
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
1713
|
-
stdout += data.toString()
|
|
1714
|
-
})
|
|
1715
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1716
|
-
stderr += data.toString()
|
|
1717
|
-
})
|
|
1718
|
-
|
|
1719
|
-
const timeout = setTimeout(() => {
|
|
1720
|
-
proc.kill('SIGTERM')
|
|
1721
|
-
reject(new Error('Query timed out after 60 seconds'))
|
|
1722
|
-
}, 60000)
|
|
1723
|
-
|
|
1724
|
-
proc.on('error', (err) => {
|
|
1725
|
-
clearTimeout(timeout)
|
|
1726
|
-
reject(err)
|
|
1727
|
-
})
|
|
1728
|
-
|
|
1729
|
-
proc.on('close', (code) => {
|
|
1730
|
-
clearTimeout(timeout)
|
|
1731
|
-
if (code !== 0) {
|
|
1732
|
-
reject(new Error(stderr || `mongosh exited with code ${code}`))
|
|
1733
|
-
return
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
try {
|
|
1737
|
-
// Extract JSON from output (mongosh may output extra info)
|
|
1738
|
-
const jsonStart = stdout.indexOf('[')
|
|
1739
|
-
const jsonEnd = stdout.lastIndexOf(']')
|
|
1740
|
-
|
|
1741
|
-
if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
|
|
1742
|
-
const jsonStr = stdout.substring(jsonStart, jsonEnd + 1)
|
|
1743
|
-
resolve(parseMongoDBResult(jsonStr))
|
|
1744
|
-
} else {
|
|
1745
|
-
// Try parsing as single object or scalar
|
|
1746
|
-
const objStart = stdout.indexOf('{')
|
|
1747
|
-
const objEnd = stdout.lastIndexOf('}')
|
|
1748
|
-
if (objStart !== -1 && objEnd !== -1 && objEnd > objStart) {
|
|
1749
|
-
const jsonStr = stdout.substring(objStart, objEnd + 1)
|
|
1750
|
-
resolve(parseMongoDBResult(jsonStr))
|
|
1751
|
-
} else {
|
|
1752
|
-
// Return as scalar result
|
|
1753
|
-
resolve({
|
|
1754
|
-
columns: ['result'],
|
|
1755
|
-
rows: [{ result: stdout.trim() }],
|
|
1756
|
-
rowCount: 1,
|
|
1757
|
-
})
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
} catch (error) {
|
|
1761
|
-
reject(
|
|
1762
|
-
new Error(
|
|
1763
|
-
`Failed to parse query result: ${error instanceof Error ? error.message : error}`,
|
|
1764
|
-
),
|
|
1765
|
-
)
|
|
1766
|
-
}
|
|
1767
|
-
})
|
|
1768
|
-
})
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
/**
|
|
1772
|
-
* List all user databases, excluding system databases (admin, config, local).
|
|
1773
|
-
* FerretDB uses MongoDB protocol, so same approach as MongoDB.
|
|
1774
|
-
*/
|
|
1775
|
-
async listDatabases(container: ContainerConfig): Promise<string[]> {
|
|
1776
|
-
const { port } = container
|
|
1777
|
-
const mongosh = await this.getMongoshPath()
|
|
1778
|
-
|
|
1779
|
-
return new Promise((resolve, reject) => {
|
|
1780
|
-
// Use JSON output for reliable parsing
|
|
1781
|
-
const script = `JSON.stringify(db.adminCommand({listDatabases: 1}).databases.map(d => d.name))`
|
|
1782
|
-
const args = [
|
|
1783
|
-
'--quiet',
|
|
1784
|
-
'--host',
|
|
1785
|
-
'127.0.0.1',
|
|
1786
|
-
'--port',
|
|
1787
|
-
String(port),
|
|
1788
|
-
'--eval',
|
|
1789
|
-
script,
|
|
1790
|
-
]
|
|
1791
|
-
|
|
1792
|
-
const proc = spawn(mongosh, args, {
|
|
1793
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1794
|
-
})
|
|
1795
|
-
|
|
1796
|
-
let stdout = ''
|
|
1797
|
-
let stderr = ''
|
|
1798
|
-
|
|
1799
|
-
proc.stdout?.on('data', (data: Buffer) => {
|
|
1800
|
-
stdout += data.toString()
|
|
1801
|
-
})
|
|
1802
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1803
|
-
stderr += data.toString()
|
|
1804
|
-
})
|
|
1805
|
-
|
|
1806
|
-
proc.on('error', reject)
|
|
1807
|
-
|
|
1808
|
-
proc.on('close', (code) => {
|
|
1809
|
-
if (code !== 0) {
|
|
1810
|
-
reject(new Error(stderr || `mongosh exited with code ${code}`))
|
|
1811
|
-
return
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
try {
|
|
1815
|
-
const allDatabases = JSON.parse(stdout.trim()) as string[]
|
|
1816
|
-
const systemDatabases = ['admin', 'config', 'local']
|
|
1817
|
-
const databases = allDatabases.filter(
|
|
1818
|
-
(db) => !systemDatabases.includes(db),
|
|
1819
|
-
)
|
|
1820
|
-
resolve(databases)
|
|
1821
|
-
} catch (error) {
|
|
1822
|
-
reject(new Error(`Failed to parse database list: ${error}`))
|
|
1823
|
-
}
|
|
1824
|
-
})
|
|
1825
|
-
})
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
async createUser(
|
|
1829
|
-
container: ContainerConfig,
|
|
1830
|
-
options: CreateUserOptions,
|
|
1831
|
-
): Promise<UserCredentials> {
|
|
1832
|
-
const { username, password, database } = options
|
|
1833
|
-
assertValidUsername(username)
|
|
1834
|
-
const { port } = container
|
|
1835
|
-
const db = database ?? container.database ?? 'admin'
|
|
1836
|
-
assertValidDatabaseName(db)
|
|
1837
|
-
const mongosh = await this.getMongoshPath()
|
|
1838
|
-
|
|
1839
|
-
// Same as MongoDB - auth disabled with --no-auth but user is still created
|
|
1840
|
-
// Use JSON.stringify for password to safely escape all special characters in JS context
|
|
1841
|
-
// Pass script via stdin to avoid exposing passwords in process listings
|
|
1842
|
-
const jsonPwd = JSON.stringify(password)
|
|
1843
|
-
const script = `db.getSiblingDB('${db}').createUser({user:'${username}',pwd:${jsonPwd},roles:[{role:'readWrite',db:'${db}'}]})`
|
|
1844
|
-
|
|
1845
|
-
const mongoshArgs = ['--host', '127.0.0.1', '--port', String(port), 'admin']
|
|
1846
|
-
|
|
1847
|
-
const runMongoshViaStdin = (js: string): Promise<void> =>
|
|
1848
|
-
new Promise((resolve, reject) => {
|
|
1849
|
-
const proc = spawn(mongosh, mongoshArgs, {
|
|
1850
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1851
|
-
})
|
|
1852
|
-
|
|
1853
|
-
let stderr = ''
|
|
1854
|
-
proc.stderr?.on('data', (data: Buffer) => {
|
|
1855
|
-
stderr += data.toString()
|
|
1856
|
-
})
|
|
1857
|
-
|
|
1858
|
-
const timeout = setTimeout(() => {
|
|
1859
|
-
proc.kill('SIGTERM')
|
|
1860
|
-
reject(new Error('mongosh timed out after 10 seconds'))
|
|
1861
|
-
}, 10000)
|
|
1862
|
-
|
|
1863
|
-
proc.on('error', (err) => {
|
|
1864
|
-
clearTimeout(timeout)
|
|
1865
|
-
reject(err)
|
|
1866
|
-
})
|
|
1867
|
-
|
|
1868
|
-
proc.on('close', (code) => {
|
|
1869
|
-
clearTimeout(timeout)
|
|
1870
|
-
if (code === 0) resolve()
|
|
1871
|
-
else reject(new Error(stderr || `mongosh exited with code ${code}`))
|
|
1872
|
-
})
|
|
1873
|
-
|
|
1874
|
-
proc.stdin?.write(js)
|
|
1875
|
-
proc.stdin?.end()
|
|
1876
|
-
})
|
|
1877
|
-
|
|
1878
|
-
try {
|
|
1879
|
-
await runMongoshViaStdin(script)
|
|
1880
|
-
} catch (error) {
|
|
1881
|
-
const err = error as Error
|
|
1882
|
-
if (
|
|
1883
|
-
err.message.includes('51003') ||
|
|
1884
|
-
err.message.includes('already exists')
|
|
1885
|
-
) {
|
|
1886
|
-
// User exists — update password instead
|
|
1887
|
-
const updateScript = `db.getSiblingDB('${db}').updateUser('${username}',{pwd:${jsonPwd}})`
|
|
1888
|
-
await runMongoshViaStdin(updateScript)
|
|
1889
|
-
} else {
|
|
1890
|
-
throw error
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
const connectionString = `mongodb://${encodeURIComponent(username)}:${encodeURIComponent(password)}@127.0.0.1:${port}/${db}`
|
|
1895
|
-
|
|
1896
|
-
return {
|
|
1897
|
-
username,
|
|
1898
|
-
password,
|
|
1899
|
-
connectionString,
|
|
1900
|
-
engine: container.engine,
|
|
1901
|
-
container: container.name,
|
|
1902
|
-
database: db,
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
export const ferretdbEngine = new FerretDBEngine()
|