spindb 0.46.5 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/cli/commands/info.js +12 -2
  2. package/dist/cli/commands/info.js.map +1 -1
  3. package/dist/cli/commands/link.js +6 -0
  4. package/dist/cli/commands/link.js.map +1 -1
  5. package/dist/cli/commands/list.js +4 -1
  6. package/dist/cli/commands/list.js.map +1 -1
  7. package/dist/cli/commands/menu/container-handlers.js +25 -7
  8. package/dist/cli/commands/menu/container-handlers.js.map +1 -1
  9. package/dist/config/backup-formats.js +11 -11
  10. package/dist/config/backup-formats.js.map +1 -1
  11. package/dist/config/version.js +1 -1
  12. package/dist/core/credential-manager.js +34 -11
  13. package/dist/core/credential-manager.js.map +1 -1
  14. package/dist/core/query-parser.js +55 -1
  15. package/dist/core/query-parser.js.map +1 -1
  16. package/dist/core/remote-container.js +11 -0
  17. package/dist/core/remote-container.js.map +1 -1
  18. package/dist/engines/clickhouse/backup.js +42 -10
  19. package/dist/engines/clickhouse/backup.js.map +1 -1
  20. package/dist/engines/clickhouse/restore.js +41 -10
  21. package/dist/engines/clickhouse/restore.js.map +1 -1
  22. package/dist/engines/cockroachdb/backup.js +18 -22
  23. package/dist/engines/cockroachdb/backup.js.map +1 -1
  24. package/dist/engines/cockroachdb/cli-utils.js +66 -0
  25. package/dist/engines/cockroachdb/cli-utils.js.map +1 -1
  26. package/dist/engines/cockroachdb/index.js +199 -116
  27. package/dist/engines/cockroachdb/index.js.map +1 -1
  28. package/dist/engines/cockroachdb/restore.js +19 -26
  29. package/dist/engines/cockroachdb/restore.js.map +1 -1
  30. package/dist/engines/couchdb/backup.js +13 -4
  31. package/dist/engines/couchdb/backup.js.map +1 -1
  32. package/dist/engines/couchdb/index.js +93 -25
  33. package/dist/engines/couchdb/index.js.map +1 -1
  34. package/dist/engines/couchdb/restore.js +15 -4
  35. package/dist/engines/couchdb/restore.js.map +1 -1
  36. package/dist/engines/ferretdb/backup.js +88 -91
  37. package/dist/engines/ferretdb/backup.js.map +1 -1
  38. package/dist/engines/ferretdb/index.js +182 -227
  39. package/dist/engines/ferretdb/index.js.map +1 -1
  40. package/dist/engines/ferretdb/restore.js +223 -20
  41. package/dist/engines/ferretdb/restore.js.map +1 -1
  42. package/dist/engines/influxdb/api-client.js +1 -1
  43. package/dist/engines/influxdb/api-client.js.map +1 -1
  44. package/dist/engines/influxdb/backup.js +25 -5
  45. package/dist/engines/influxdb/backup.js.map +1 -1
  46. package/dist/engines/influxdb/index.js +165 -43
  47. package/dist/engines/influxdb/index.js.map +1 -1
  48. package/dist/engines/influxdb/restore.js +22 -2
  49. package/dist/engines/influxdb/restore.js.map +1 -1
  50. package/dist/engines/mariadb/backup.js +24 -15
  51. package/dist/engines/mariadb/backup.js.map +1 -1
  52. package/dist/engines/mariadb/env.js +11 -0
  53. package/dist/engines/mariadb/env.js.map +1 -0
  54. package/dist/engines/mariadb/index.js +192 -113
  55. package/dist/engines/mariadb/index.js.map +1 -1
  56. package/dist/engines/mariadb/restore.js +28 -5
  57. package/dist/engines/mariadb/restore.js.map +1 -1
  58. package/dist/engines/meilisearch/backup.js +8 -4
  59. package/dist/engines/meilisearch/backup.js.map +1 -1
  60. package/dist/engines/meilisearch/index.js +55 -58
  61. package/dist/engines/meilisearch/index.js.map +1 -1
  62. package/dist/engines/mongo-uri.js +11 -0
  63. package/dist/engines/mongo-uri.js.map +1 -0
  64. package/dist/engines/mongodb/backup.js +62 -13
  65. package/dist/engines/mongodb/backup.js.map +1 -1
  66. package/dist/engines/mongodb/index.js +170 -108
  67. package/dist/engines/mongodb/index.js.map +1 -1
  68. package/dist/engines/mongodb/restore.js +21 -1
  69. package/dist/engines/mongodb/restore.js.map +1 -1
  70. package/dist/engines/mysql/backup.js +24 -7
  71. package/dist/engines/mysql/backup.js.map +1 -1
  72. package/dist/engines/mysql/index.js +154 -89
  73. package/dist/engines/mysql/index.js.map +1 -1
  74. package/dist/engines/mysql/restore.js +14 -4
  75. package/dist/engines/mysql/restore.js.map +1 -1
  76. package/dist/engines/postgresql/backup.js +9 -2
  77. package/dist/engines/postgresql/backup.js.map +1 -1
  78. package/dist/engines/postgresql/index.js +45 -17
  79. package/dist/engines/postgresql/index.js.map +1 -1
  80. package/dist/engines/postgresql/restore.js +7 -3
  81. package/dist/engines/postgresql/restore.js.map +1 -1
  82. package/dist/engines/qdrant/backup.js +5 -1
  83. package/dist/engines/qdrant/backup.js.map +1 -1
  84. package/dist/engines/qdrant/index.js +31 -2
  85. package/dist/engines/qdrant/index.js.map +1 -1
  86. package/dist/engines/qdrant/restore.js +5 -3
  87. package/dist/engines/qdrant/restore.js.map +1 -1
  88. package/dist/engines/questdb/auth.js +26 -0
  89. package/dist/engines/questdb/auth.js.map +1 -0
  90. package/dist/engines/questdb/backup.js +10 -8
  91. package/dist/engines/questdb/backup.js.map +1 -1
  92. package/dist/engines/questdb/index.js +16 -8
  93. package/dist/engines/questdb/index.js.map +1 -1
  94. package/dist/engines/questdb/restore.js +7 -5
  95. package/dist/engines/questdb/restore.js.map +1 -1
  96. package/dist/engines/redis/backup.js +45 -16
  97. package/dist/engines/redis/backup.js.map +1 -1
  98. package/dist/engines/redis/cli-common.js +62 -0
  99. package/dist/engines/redis/cli-common.js.map +1 -0
  100. package/dist/engines/redis/index.js +31 -8
  101. package/dist/engines/redis/index.js.map +1 -1
  102. package/dist/engines/redis/restore.js +59 -9
  103. package/dist/engines/redis/restore.js.map +1 -1
  104. package/dist/engines/surrealdb/auth.js +98 -0
  105. package/dist/engines/surrealdb/auth.js.map +1 -0
  106. package/dist/engines/surrealdb/backup.js +72 -9
  107. package/dist/engines/surrealdb/backup.js.map +1 -1
  108. package/dist/engines/surrealdb/index.js +84 -144
  109. package/dist/engines/surrealdb/index.js.map +1 -1
  110. package/dist/engines/surrealdb/restore.js +32 -31
  111. package/dist/engines/surrealdb/restore.js.map +1 -1
  112. package/dist/engines/typedb/backup.js +9 -1
  113. package/dist/engines/typedb/backup.js.map +1 -1
  114. package/dist/engines/typedb/cli-utils.js +3 -3
  115. package/dist/engines/typedb/cli-utils.js.map +1 -1
  116. package/dist/engines/typedb/index.js +9 -2
  117. package/dist/engines/typedb/index.js.map +1 -1
  118. package/dist/engines/typedb/restore.js +19 -7
  119. package/dist/engines/typedb/restore.js.map +1 -1
  120. package/dist/engines/valkey/backup.js +37 -13
  121. package/dist/engines/valkey/backup.js.map +1 -1
  122. package/dist/engines/valkey/index.js +207 -58
  123. package/dist/engines/valkey/index.js.map +1 -1
  124. package/dist/engines/valkey/restore.js +21 -2
  125. package/dist/engines/valkey/restore.js.map +1 -1
  126. package/dist/engines/weaviate/backup.js +7 -2
  127. package/dist/engines/weaviate/backup.js.map +1 -1
  128. package/dist/types/index.js.map +1 -1
  129. package/package.json +1 -1
@@ -10,7 +10,7 @@
10
10
  * - Uses PostgreSQL wire protocol for client connections
11
11
  * - Single binary: `cockroach` (handles server, sql client, and admin tasks)
12
12
  * - Default database: `defaultdb`
13
- * - Default user: `root` (no password in insecure mode)
13
+ * - Default local admin user: `root` authenticated via generated client cert
14
14
  */
15
15
  import { spawn } from 'child_process';
16
16
  import { existsSync } from 'fs';
@@ -30,10 +30,80 @@ import { normalizeVersion, SUPPORTED_MAJOR_VERSIONS, COCKROACHDB_VERSION_MAP, }
30
30
  import { fetchAvailableVersions as fetchHostdbVersions } from './hostdb-releases.js';
31
31
  import { detectBackupFormat as detectBackupFormatImpl, restoreBackup, } from './restore.js';
32
32
  import { createBackup } from './backup.js';
33
- import { validateCockroachIdentifier, escapeCockroachIdentifier, escapeSqlValue, parseCsvLine, parseCsvRecords, isInsecureConnection, } from './cli-utils.js';
33
+ import { validateCockroachIdentifier, escapeCockroachIdentifier, escapeSqlValue, parseCsvLine, parseCsvRecords, isInsecureConnection, buildLocalCockroachSqlArgs, buildSecureCockroachConnectionString, buildInsecureCockroachConnectionString, getCockroachCertsDir, getCockroachCaCertPath, getCockroachCaKeyPath, getCockroachClientCertPath, getCockroachClientKeyPath, } from './cli-utils.js';
34
34
  import { parseCSVToQueryResult } from '../../core/query-parser.js';
35
35
  const ENGINE = 'cockroachdb';
36
36
  const engineDef = getEngineDefaults(ENGINE);
37
+ async function runCockroachCommand(cockroachPath, args, spawnOptions = {}) {
38
+ return new Promise((resolve, reject) => {
39
+ const proc = spawn(cockroachPath, args, {
40
+ stdio: ['ignore', 'pipe', 'pipe'],
41
+ ...spawnOptions,
42
+ });
43
+ let stdout = '';
44
+ let stderr = '';
45
+ proc.stdout?.on('data', (data) => {
46
+ stdout += data.toString();
47
+ });
48
+ proc.stderr?.on('data', (data) => {
49
+ stderr += data.toString();
50
+ });
51
+ proc.on('error', reject);
52
+ proc.on('close', (code) => {
53
+ if (code === 0) {
54
+ resolve({ stdout, stderr });
55
+ }
56
+ else {
57
+ reject(new Error(stderr || `cockroach exited with code ${code}`));
58
+ }
59
+ });
60
+ });
61
+ }
62
+ async function ensureSecureLocalAssets(cockroachPath, containerName, bindAddress) {
63
+ const certsDir = getCockroachCertsDir(containerName);
64
+ const caKey = getCockroachCaKeyPath(containerName);
65
+ const nodeCert = join(certsDir, 'node.crt');
66
+ const nodeKey = join(certsDir, 'node.key');
67
+ const rootClientCert = getCockroachClientCertPath(containerName, 'root');
68
+ const rootClientKey = getCockroachClientKeyPath(containerName, 'root');
69
+ await mkdir(certsDir, { recursive: true });
70
+ if (!existsSync(join(certsDir, 'ca.crt')) || !existsSync(caKey)) {
71
+ await runCockroachCommand(cockroachPath, [
72
+ 'cert',
73
+ 'create-ca',
74
+ '--certs-dir',
75
+ certsDir,
76
+ '--ca-key',
77
+ caKey,
78
+ ]);
79
+ }
80
+ if (!existsSync(nodeCert) || !existsSync(nodeKey)) {
81
+ const hosts = new Set(['127.0.0.1', 'localhost', '::1']);
82
+ if (bindAddress && bindAddress !== '0.0.0.0' && bindAddress !== '::') {
83
+ hosts.add(bindAddress);
84
+ }
85
+ await runCockroachCommand(cockroachPath, [
86
+ 'cert',
87
+ 'create-node',
88
+ ...Array.from(hosts),
89
+ '--certs-dir',
90
+ certsDir,
91
+ '--ca-key',
92
+ caKey,
93
+ ]);
94
+ }
95
+ if (!existsSync(rootClientCert) || !existsSync(rootClientKey)) {
96
+ await runCockroachCommand(cockroachPath, [
97
+ 'cert',
98
+ 'create-client',
99
+ 'root',
100
+ '--certs-dir',
101
+ certsDir,
102
+ '--ca-key',
103
+ caKey,
104
+ ]);
105
+ }
106
+ }
37
107
  export class CockroachDBEngine extends BaseEngine {
38
108
  name = ENGINE;
39
109
  displayName = 'CockroachDB';
@@ -168,11 +238,13 @@ export class CockroachDBEngine extends BaseEngine {
168
238
  const httpPort = port + 1; // HTTP admin UI port
169
239
  onProgress?.({ stage: 'starting', message: 'Starting CockroachDB...' });
170
240
  logDebug(`Starting CockroachDB with data dir: ${dataDir}`);
241
+ await ensureSecureLocalAssets(cockroachBinary, name, container.bindAddress);
171
242
  // CockroachDB start command
172
- // Using --insecure for local development (no TLS)
243
+ // Local containers run in secure mode with per-container certs.
173
244
  const args = [
174
245
  'start-single-node',
175
- '--insecure',
246
+ '--certs-dir',
247
+ getCockroachCertsDir(name),
176
248
  '--store',
177
249
  dataDir,
178
250
  '--listen-addr',
@@ -262,7 +334,7 @@ export class CockroachDBEngine extends BaseEngine {
262
334
  // Windows needs a longer timeout since CockroachDB initialization takes more time
263
335
  const timeout = isWindows ? 120000 : 60000;
264
336
  logDebug(`Waiting for CockroachDB server to be ready on port ${port}... (timeout: ${timeout}ms)`);
265
- const ready = await this.waitForReady(port, version, timeout);
337
+ const ready = await this.waitForReady(name, port, version, timeout);
266
338
  logDebug(`waitForReady returned: ${ready}`);
267
339
  if (!ready) {
268
340
  // Clean up the spawned process and PID file before throwing
@@ -288,7 +360,7 @@ export class CockroachDBEngine extends BaseEngine {
288
360
  };
289
361
  }
290
362
  // Wait for CockroachDB to be ready
291
- async waitForReady(port, version, timeoutMs = 60000) {
363
+ async waitForReady(containerName, port, version, timeoutMs = 60000) {
292
364
  logDebug(`waitForReady called for port ${port}, version ${version}`);
293
365
  const startTime = Date.now();
294
366
  const checkInterval = 500;
@@ -310,14 +382,11 @@ export class CockroachDBEngine extends BaseEngine {
310
382
  attempt++;
311
383
  logDebug(`Connection attempt ${attempt}...`);
312
384
  try {
313
- const args = [
314
- 'sql',
315
- '--insecure',
316
- '--host',
317
- `127.0.0.1:${port}`,
318
- '--execute',
319
- 'SELECT 1',
320
- ];
385
+ const args = buildLocalCockroachSqlArgs({
386
+ containerName,
387
+ port,
388
+ });
389
+ args.push('--execute', 'SELECT 1');
321
390
  await new Promise((resolve, reject) => {
322
391
  const proc = spawn(cockroach, args, {
323
392
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -403,18 +472,15 @@ export class CockroachDBEngine extends BaseEngine {
403
472
  }
404
473
  // Get CockroachDB server status
405
474
  async status(container) {
406
- const { port, version } = container;
475
+ const { name, port, version } = container;
407
476
  // Try to connect
408
477
  try {
409
478
  const cockroach = await this.getCockroachPath(version);
410
- const args = [
411
- 'sql',
412
- '--insecure',
413
- '--host',
414
- `127.0.0.1:${port}`,
415
- '--execute',
416
- 'SELECT 1',
417
- ];
479
+ const args = buildLocalCockroachSqlArgs({
480
+ containerName: name,
481
+ port,
482
+ });
483
+ args.push('--execute', 'SELECT 1');
418
484
  await new Promise((resolve, reject) => {
419
485
  const proc = spawn(cockroach, args, {
420
486
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -452,23 +518,40 @@ export class CockroachDBEngine extends BaseEngine {
452
518
  }
453
519
  /**
454
520
  * Get connection string
455
- * Format: postgresql://root@127.0.0.1:PORT/DATABASE?sslmode=disable
521
+ * Format: postgresql://root@127.0.0.1:PORT/DATABASE?sslmode=verify-full...
456
522
  */
457
523
  getConnectionString(container, database) {
458
- const { port } = container;
524
+ const { name, port } = container;
459
525
  const db = database || container.database || 'defaultdb';
460
- return `postgresql://root@127.0.0.1:${port}/${db}?sslmode=disable`;
526
+ const legacyInsecureRunning = container.status === 'running' &&
527
+ !existsSync(getCockroachCaCertPath(name));
528
+ if (legacyInsecureRunning) {
529
+ return buildInsecureCockroachConnectionString({
530
+ port,
531
+ database: db,
532
+ });
533
+ }
534
+ return buildSecureCockroachConnectionString({
535
+ containerName: name,
536
+ port,
537
+ database: db,
538
+ });
461
539
  }
462
540
  // Open cockroach sql interactive shell
463
541
  async connect(container, database) {
464
- const { port, version } = container;
542
+ const { name, port, version } = container;
465
543
  const db = database || container.database || 'defaultdb';
466
544
  const cockroach = await this.getCockroachPath(version);
545
+ const args = buildLocalCockroachSqlArgs({
546
+ containerName: name,
547
+ port,
548
+ database: db,
549
+ });
467
550
  const spawnOptions = {
468
551
  stdio: 'inherit',
469
552
  };
470
553
  return new Promise((resolve, reject) => {
471
- const proc = spawn(cockroach, ['sql', '--insecure', '--host', `127.0.0.1:${port}`, '--database', db], spawnOptions);
554
+ const proc = spawn(cockroach, args, spawnOptions);
472
555
  proc.on('error', reject);
473
556
  proc.on('close', () => resolve());
474
557
  });
@@ -477,19 +560,16 @@ export class CockroachDBEngine extends BaseEngine {
477
560
  * Create a new database
478
561
  */
479
562
  async createDatabase(container, database) {
480
- const { port, version } = container;
563
+ const { name, port, version } = container;
481
564
  // Validate database identifier to prevent SQL injection
482
565
  validateCockroachIdentifier(database, 'database');
483
566
  const escapedDb = escapeCockroachIdentifier(database);
484
567
  const cockroach = await this.getCockroachPath(version);
485
- const args = [
486
- 'sql',
487
- '--insecure',
488
- '--host',
489
- `127.0.0.1:${port}`,
490
- '--execute',
491
- `CREATE DATABASE IF NOT EXISTS ${escapedDb}`,
492
- ];
568
+ const args = buildLocalCockroachSqlArgs({
569
+ containerName: name,
570
+ port,
571
+ });
572
+ args.push('--execute', `CREATE DATABASE IF NOT EXISTS ${escapedDb}`);
493
573
  await new Promise((resolve, reject) => {
494
574
  const proc = spawn(cockroach, args, {
495
575
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -514,7 +594,7 @@ export class CockroachDBEngine extends BaseEngine {
514
594
  * Drop a database
515
595
  */
516
596
  async dropDatabase(container, database) {
517
- const { port, version } = container;
597
+ const { name, port, version } = container;
518
598
  // Don't allow dropping system databases
519
599
  const systemDatabases = ['defaultdb', 'postgres', 'system'];
520
600
  if (systemDatabases.includes(database.toLowerCase())) {
@@ -524,14 +604,11 @@ export class CockroachDBEngine extends BaseEngine {
524
604
  validateCockroachIdentifier(database, 'database');
525
605
  const escapedDb = escapeCockroachIdentifier(database);
526
606
  const cockroach = await this.getCockroachPath(version);
527
- const args = [
528
- 'sql',
529
- '--insecure',
530
- '--host',
531
- `127.0.0.1:${port}`,
532
- '--execute',
533
- `DROP DATABASE IF EXISTS ${escapedDb}`,
534
- ];
607
+ const args = buildLocalCockroachSqlArgs({
608
+ containerName: name,
609
+ port,
610
+ });
611
+ args.push('--execute', `DROP DATABASE IF EXISTS ${escapedDb}`);
535
612
  await new Promise((resolve, reject) => {
536
613
  const proc = spawn(cockroach, args, {
537
614
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -556,7 +633,7 @@ export class CockroachDBEngine extends BaseEngine {
556
633
  * Rename a database using CockroachDB's native ALTER DATABASE RENAME
557
634
  */
558
635
  async renameDatabase(container, oldName, newName) {
559
- const { port, version } = container;
636
+ const { name, port, version } = container;
560
637
  const systemDatabases = ['defaultdb', 'postgres', 'system'];
561
638
  if (systemDatabases.includes(oldName.toLowerCase())) {
562
639
  throw new Error(`Cannot rename system database: ${oldName}`);
@@ -569,14 +646,11 @@ export class CockroachDBEngine extends BaseEngine {
569
646
  const escapedOld = escapeCockroachIdentifier(oldName);
570
647
  const escapedNew = escapeCockroachIdentifier(newName);
571
648
  const cockroach = await this.getCockroachPath(version);
572
- const args = [
573
- 'sql',
574
- '--insecure',
575
- '--host',
576
- `127.0.0.1:${port}`,
577
- '--execute',
578
- `ALTER DATABASE ${escapedOld} RENAME TO ${escapedNew}`,
579
- ];
649
+ const args = buildLocalCockroachSqlArgs({
650
+ containerName: name,
651
+ port,
652
+ });
653
+ args.push('--execute', `ALTER DATABASE ${escapedOld} RENAME TO ${escapedNew}`);
580
654
  await new Promise((resolve, reject) => {
581
655
  const proc = spawn(cockroach, args, {
582
656
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -601,7 +675,7 @@ export class CockroachDBEngine extends BaseEngine {
601
675
  * Get the database size in bytes
602
676
  */
603
677
  async getDatabaseSize(container) {
604
- const { port, version, database } = container;
678
+ const { name, port, version, database } = container;
605
679
  const db = database || 'defaultdb';
606
680
  try {
607
681
  const cockroach = await this.getCockroachPath(version);
@@ -609,17 +683,12 @@ export class CockroachDBEngine extends BaseEngine {
609
683
  // CockroachDB query to get database size
610
684
  const query = `SELECT sum(range_size_mb) * 1024 * 1024 as size_bytes FROM [SHOW RANGES FROM DATABASE ${escapeCockroachIdentifier(db)}]`;
611
685
  const result = await new Promise((resolve, reject) => {
612
- const args = [
613
- 'sql',
614
- '--insecure',
615
- '--host',
616
- `127.0.0.1:${port}`,
617
- '--database',
618
- db,
619
- '--execute',
620
- query,
621
- '--format=csv',
622
- ];
686
+ const args = buildLocalCockroachSqlArgs({
687
+ containerName: name,
688
+ port,
689
+ database: db,
690
+ });
691
+ args.push('--execute', query, '--format=csv');
623
692
  const proc = spawn(cockroach, args, {
624
693
  stdio: ['ignore', 'pipe', 'pipe'],
625
694
  });
@@ -852,21 +921,17 @@ export class CockroachDBEngine extends BaseEngine {
852
921
  }
853
922
  // Run a SQL file or inline SQL statement
854
923
  async runScript(container, options) {
855
- const { port, version } = container;
924
+ const { name, port, version } = container;
856
925
  const db = options.database || container.database || 'defaultdb';
857
926
  const cockroach = await this.getCockroachPath(version);
858
927
  if (options.file) {
859
928
  // Run SQL file
860
- const args = [
861
- 'sql',
862
- '--insecure',
863
- '--host',
864
- `127.0.0.1:${port}`,
865
- '--database',
866
- db,
867
- '--file',
868
- options.file,
869
- ];
929
+ const args = buildLocalCockroachSqlArgs({
930
+ containerName: name,
931
+ port,
932
+ database: db,
933
+ });
934
+ args.push('--file', options.file);
870
935
  await new Promise((resolve, reject) => {
871
936
  const proc = spawn(cockroach, args, {
872
937
  stdio: 'inherit',
@@ -887,14 +952,11 @@ export class CockroachDBEngine extends BaseEngine {
887
952
  }
888
953
  else if (options.sql) {
889
954
  // Run inline SQL via stdin
890
- const args = [
891
- 'sql',
892
- '--insecure',
893
- '--host',
894
- `127.0.0.1:${port}`,
895
- '--database',
896
- db,
897
- ];
955
+ const args = buildLocalCockroachSqlArgs({
956
+ containerName: name,
957
+ port,
958
+ database: db,
959
+ });
898
960
  await new Promise((resolve, reject) => {
899
961
  const proc = spawn(cockroach, args, {
900
962
  stdio: ['pipe', 'inherit', 'inherit'],
@@ -923,19 +985,27 @@ export class CockroachDBEngine extends BaseEngine {
923
985
  * Execute a SQL query and return structured results
924
986
  */
925
987
  async executeQuery(container, query, options) {
926
- const { port, version } = container;
988
+ const { name, port, version } = container;
927
989
  const db = options?.database || container.database || 'defaultdb';
928
990
  const cockroach = await this.getCockroachPath(version);
929
991
  return new Promise((resolve, reject) => {
930
- const args = ['sql'];
931
- if (options?.password) {
932
- const user = options.username || 'root';
933
- const encodedPass = encodeURIComponent(options.password);
934
- args.push('--url', `postgresql://${user}:${encodedPass}@127.0.0.1:${port}/${db}?sslmode=disable`);
935
- }
936
- else {
937
- args.push('--insecure', '--host', `127.0.0.1:${port}`, '--database', db);
938
- }
992
+ const args = options?.host
993
+ ? (() => {
994
+ const username = options.username || 'root';
995
+ const remoteUrl = new URL(`postgresql://${encodeURIComponent(username)}@${options.host}:${port}/${db}`);
996
+ if (options.password) {
997
+ remoteUrl.password = options.password;
998
+ }
999
+ remoteUrl.searchParams.set('sslmode', options.ssl === false ? 'disable' : 'require');
1000
+ return ['sql', '--url', remoteUrl.toString()];
1001
+ })()
1002
+ : buildLocalCockroachSqlArgs({
1003
+ containerName: name,
1004
+ port,
1005
+ database: db,
1006
+ username: options?.username,
1007
+ password: options?.password,
1008
+ });
939
1009
  args.push('--execute', query, '--format=csv');
940
1010
  const proc = spawn(cockroach, args, {
941
1011
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -967,18 +1037,14 @@ export class CockroachDBEngine extends BaseEngine {
967
1037
  * List all user databases, excluding system databases (defaultdb, postgres, system).
968
1038
  */
969
1039
  async listDatabases(container) {
970
- const { port, version } = container;
1040
+ const { name, port, version } = container;
971
1041
  const cockroach = await this.getCockroachPath(version);
972
1042
  return new Promise((resolve, reject) => {
973
- const args = [
974
- 'sql',
975
- '--insecure',
976
- '--host',
977
- `127.0.0.1:${port}`,
978
- '--execute',
979
- `SHOW DATABASES`,
980
- '--format=csv',
981
- ];
1043
+ const args = buildLocalCockroachSqlArgs({
1044
+ containerName: name,
1045
+ port,
1046
+ });
1047
+ args.push('--execute', `SHOW DATABASES`, '--format=csv');
982
1048
  const proc = spawn(cockroach, args, {
983
1049
  stdio: ['ignore', 'pipe', 'pipe'],
984
1050
  });
@@ -1010,18 +1076,32 @@ export class CockroachDBEngine extends BaseEngine {
1010
1076
  async createUser(container, options) {
1011
1077
  const { username, password, database } = options;
1012
1078
  assertValidUsername(username);
1013
- const { port, version } = container;
1079
+ const { name, port, version } = container;
1014
1080
  const db = database || container.database || 'defaultdb';
1015
1081
  validateCockroachIdentifier(username, 'user');
1016
1082
  validateCockroachIdentifier(db, 'database');
1017
1083
  const escapedUser = escapeCockroachIdentifier(username);
1018
1084
  const escapedDb = escapeCockroachIdentifier(db);
1019
1085
  const cockroach = await this.getCockroachPath(version);
1020
- // CockroachDB in insecure mode doesn't support passwords.
1021
- // Create user without password and grant privileges.
1022
- // SQL is sent via stdin to avoid shell escaping issues with --execute.
1023
- const sql = `CREATE USER IF NOT EXISTS ${escapedUser}; GRANT ALL ON DATABASE ${escapedDb} TO ${escapedUser};`;
1024
- const args = ['sql', '--insecure', '--host', `127.0.0.1:${port}`];
1086
+ // Local CockroachDB containers run with TLS enabled. Bootstrap admin commands
1087
+ // authenticate as root via the generated client certificate, while end users
1088
+ // connect with password-based auth over TLS.
1089
+ const escapedPassword = password.replace(/'/g, "''");
1090
+ const sql = [
1091
+ `CREATE USER IF NOT EXISTS ${escapedUser}`,
1092
+ `ALTER USER ${escapedUser} WITH PASSWORD '${escapedPassword}'`,
1093
+ `GRANT ALL ON DATABASE ${escapedDb} TO ${escapedUser}`,
1094
+ `GRANT ALL ON SCHEMA public TO ${escapedUser}`,
1095
+ `GRANT ALL ON ALL TABLES IN SCHEMA public TO ${escapedUser}`,
1096
+ `GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO ${escapedUser}`,
1097
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${escapedUser}`,
1098
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ${escapedUser}`,
1099
+ ].join('; ') + ';';
1100
+ const args = buildLocalCockroachSqlArgs({
1101
+ containerName: name,
1102
+ port,
1103
+ database: db,
1104
+ });
1025
1105
  await new Promise((resolve, reject) => {
1026
1106
  const proc = spawn(cockroach, args, {
1027
1107
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -1042,12 +1122,15 @@ export class CockroachDBEngine extends BaseEngine {
1042
1122
  proc.stdin?.write(sql);
1043
1123
  proc.stdin?.end();
1044
1124
  });
1045
- // In insecure mode, connections don't use passwords
1046
- const connectionString = `postgresql://${encodeURIComponent(username)}@127.0.0.1:${port}/${db}?sslmode=disable`;
1125
+ const connectionString = buildSecureCockroachConnectionString({
1126
+ containerName: name,
1127
+ port,
1128
+ database: db,
1129
+ username,
1130
+ password,
1131
+ });
1047
1132
  return {
1048
1133
  username,
1049
- // CockroachDB insecure mode does not enforce password authentication,
1050
- // but we return the caller-provided password for credential file consistency.
1051
1134
  password,
1052
1135
  connectionString,
1053
1136
  engine: container.engine,