spindb 0.46.4 → 0.47.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.
Files changed (127) 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 +179 -226
  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 +21 -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 +8 -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 +10 -4
  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 +48 -15
  97. package/dist/engines/redis/backup.js.map +1 -1
  98. package/dist/engines/redis/index.js +45 -12
  99. package/dist/engines/redis/index.js.map +1 -1
  100. package/dist/engines/redis/restore.js +21 -2
  101. package/dist/engines/redis/restore.js.map +1 -1
  102. package/dist/engines/surrealdb/auth.js +98 -0
  103. package/dist/engines/surrealdb/auth.js.map +1 -0
  104. package/dist/engines/surrealdb/backup.js +72 -9
  105. package/dist/engines/surrealdb/backup.js.map +1 -1
  106. package/dist/engines/surrealdb/index.js +84 -144
  107. package/dist/engines/surrealdb/index.js.map +1 -1
  108. package/dist/engines/surrealdb/restore.js +32 -31
  109. package/dist/engines/surrealdb/restore.js.map +1 -1
  110. package/dist/engines/typedb/backup.js +9 -1
  111. package/dist/engines/typedb/backup.js.map +1 -1
  112. package/dist/engines/typedb/cli-utils.js +3 -3
  113. package/dist/engines/typedb/cli-utils.js.map +1 -1
  114. package/dist/engines/typedb/index.js +9 -2
  115. package/dist/engines/typedb/index.js.map +1 -1
  116. package/dist/engines/typedb/restore.js +19 -7
  117. package/dist/engines/typedb/restore.js.map +1 -1
  118. package/dist/engines/valkey/backup.js +37 -13
  119. package/dist/engines/valkey/backup.js.map +1 -1
  120. package/dist/engines/valkey/index.js +207 -58
  121. package/dist/engines/valkey/index.js.map +1 -1
  122. package/dist/engines/valkey/restore.js +21 -2
  123. package/dist/engines/valkey/restore.js.map +1 -1
  124. package/dist/engines/weaviate/backup.js +7 -2
  125. package/dist/engines/weaviate/backup.js.map +1 -1
  126. package/dist/types/index.js.map +1 -1
  127. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
- import { spawn, exec } from 'child_process';
2
- import { promisify } from 'util';
1
+ import { spawn } from 'child_process';
3
2
  import { existsSync } from 'fs';
4
3
  import { mkdir, writeFile, readFile, unlink } from 'fs/promises';
5
- import { join } from 'path';
4
+ import { dirname, join } from 'path';
6
5
  import { BaseEngine } from '../base-engine.js';
7
6
  import { paths } from '../../config/paths.js';
8
7
  import { getEngineDefaults } from '../../config/defaults.js';
9
8
  import { platformService, isWindows } from '../../core/platform-service.js';
10
9
  import { configManager } from '../../core/config-manager.js';
10
+ import { getDefaultUsername, loadCredentials, saveCredentials, } from '../../core/credential-manager.js';
11
11
  import { logDebug, logWarning, assertValidUsername, } from '../../core/error-handler.js';
12
12
  import { processManager } from '../../core/process-manager.js';
13
13
  import { valkeyBinaryManager } from './binary-manager.js';
@@ -17,10 +17,89 @@ import { fetchAvailableVersions as fetchHostdbVersions } from './hostdb-releases
17
17
  import { detectBackupFormat as detectBackupFormatImpl, restoreBackup, } from './restore.js';
18
18
  import { createBackup } from './backup.js';
19
19
  import { getValkeyCliPath, VALKEY_CLI_NOT_FOUND_ERROR } from './cli-utils.js';
20
+ import { Engine, } from '../../types/index.js';
20
21
  import { parseRedisResult } from '../../core/query-parser.js';
21
22
  import { getLibraryEnv, detectLibraryError } from '../../core/library-env.js';
22
- const execAsync = promisify(exec);
23
23
  const ENGINE = 'valkey';
24
+ function shouldPassValkeyCliUsername(username) {
25
+ if (!username) {
26
+ return false;
27
+ }
28
+ const trimmed = username.trim();
29
+ return trimmed.length > 0 && trimmed.toLowerCase() !== 'default';
30
+ }
31
+ function buildValkeyCliEnv(libraryEnv = {}, password) {
32
+ const env = { ...process.env, ...libraryEnv };
33
+ if (password) {
34
+ env.REDISCLI_AUTH = password;
35
+ }
36
+ else {
37
+ delete env.REDISCLI_AUTH;
38
+ }
39
+ return env;
40
+ }
41
+ function getValkeyCliBaseDir(valkeyCli) {
42
+ return dirname(dirname(valkeyCli));
43
+ }
44
+ function getResolvedValkeyCliLibraryEnv(valkeyCli) {
45
+ return getLibraryEnv(getValkeyCliBaseDir(valkeyCli));
46
+ }
47
+ function getLocalCliHost(container) {
48
+ return container.bindAddress ?? '127.0.0.1';
49
+ }
50
+ async function runValkeyCliCommand(valkeyCli, args, options = {}) {
51
+ return new Promise((resolve, reject) => {
52
+ const proc = spawn(valkeyCli, args, {
53
+ stdio: ['ignore', 'pipe', 'pipe'],
54
+ env: buildValkeyCliEnv(options.libraryEnv, options.password),
55
+ });
56
+ let stdout = '';
57
+ let stderr = '';
58
+ let settled = false;
59
+ let timeoutId;
60
+ let timeoutError = null;
61
+ const finish = (error) => {
62
+ if (settled)
63
+ return;
64
+ settled = true;
65
+ if (timeoutId) {
66
+ clearTimeout(timeoutId);
67
+ }
68
+ if (error) {
69
+ reject(error);
70
+ }
71
+ else {
72
+ resolve({ stdout, stderr });
73
+ }
74
+ };
75
+ if (options.timeout && options.timeout > 0) {
76
+ timeoutId = setTimeout(() => {
77
+ timeoutError = new Error(`valkey-cli command timed out after ${options.timeout}ms`);
78
+ proc.kill();
79
+ }, options.timeout);
80
+ }
81
+ proc.stdout?.on('data', (data) => {
82
+ stdout += data.toString();
83
+ });
84
+ proc.stderr?.on('data', (data) => {
85
+ stderr += data.toString();
86
+ });
87
+ proc.on('error', (error) => {
88
+ finish(error);
89
+ });
90
+ proc.on('close', (code) => {
91
+ if (timeoutError) {
92
+ finish(timeoutError);
93
+ return;
94
+ }
95
+ if (code === 0) {
96
+ finish();
97
+ return;
98
+ }
99
+ finish(new Error(stderr || `valkey-cli exited with code ${code}`));
100
+ });
101
+ });
102
+ }
24
103
  /**
25
104
  * Escape a Valkey key for use in CLI commands.
26
105
  * Escapes backslashes, double quotes, and control characters to prevent
@@ -199,6 +278,12 @@ export class ValkeyEngine extends BaseEngine {
199
278
  displayName = 'Valkey';
200
279
  defaultPort = engineDef.defaultPort;
201
280
  supportedVersions = SUPPORTED_MAJOR_VERSIONS;
281
+ async getLocalAuth(containerName) {
282
+ const savedCreds = await loadCredentials(containerName, Engine.Valkey, getDefaultUsername(Engine.Valkey));
283
+ return savedCreds
284
+ ? { username: savedCreds.username, password: savedCreds.password }
285
+ : {};
286
+ }
202
287
  // Get platform info for binary operations
203
288
  getPlatformInfo() {
204
289
  return platformService.getPlatformInfo();
@@ -442,7 +527,7 @@ export class ValkeyEngine extends BaseEngine {
442
527
  // This follows the pattern used by MySQL which works on Windows
443
528
  return new Promise((resolve, reject) => {
444
529
  const spawnOpts = {
445
- stdio: ['ignore', 'pipe', 'pipe'],
530
+ stdio: ['ignore', 'ignore', 'ignore'],
446
531
  detached: true,
447
532
  windowsHide: true,
448
533
  env: { ...process.env, ...libraryEnv },
@@ -451,8 +536,6 @@ export class ValkeyEngine extends BaseEngine {
451
536
  const cygwinConfigPath = toCygwinPath(configPath);
452
537
  const proc = spawn(valkeyServer, [cygwinConfigPath], spawnOpts);
453
538
  let settled = false;
454
- let stderrOutput = '';
455
- let stdoutOutput = '';
456
539
  // Handle spawn errors (binary not found, DLL issues, etc.)
457
540
  proc.on('error', (err) => {
458
541
  if (settled)
@@ -460,16 +543,6 @@ export class ValkeyEngine extends BaseEngine {
460
543
  settled = true;
461
544
  reject(new Error(`Failed to spawn Valkey server: ${err.message}`));
462
545
  });
463
- proc.stdout?.on('data', (data) => {
464
- const str = data.toString();
465
- stdoutOutput += str;
466
- logDebug(`valkey-server stdout: ${str}`);
467
- });
468
- proc.stderr?.on('data', (data) => {
469
- const str = data.toString();
470
- stderrOutput += str;
471
- logDebug(`valkey-server stderr: ${str}`);
472
- });
473
546
  // Detach the process so it continues running after parent exits
474
547
  proc.unref();
475
548
  // Give spawn a moment to fail if it's going to, then check readiness
@@ -490,7 +563,7 @@ export class ValkeyEngine extends BaseEngine {
490
563
  // Non-fatal - process is running, PID file is for convenience
491
564
  }
492
565
  // Wait for Valkey to be ready
493
- const ready = await this.waitForReady(port, version);
566
+ const ready = await this.waitForReady(name, port, version, bindAddress);
494
567
  if (settled)
495
568
  return;
496
569
  if (ready) {
@@ -524,7 +597,7 @@ export class ValkeyEngine extends BaseEngine {
524
597
  logContent = '(log file not found or empty)';
525
598
  }
526
599
  // Check for library loading errors first
527
- const libError = detectLibraryError(stderrOutput + logContent, 'Valkey');
600
+ const libError = detectLibraryError(logContent, 'Valkey');
528
601
  if (libError) {
529
602
  reject(new Error(libError));
530
603
  return;
@@ -535,8 +608,6 @@ export class ValkeyEngine extends BaseEngine {
535
608
  `Config: ${configPath}`,
536
609
  `Log file: ${logFile}`,
537
610
  `Log content:\n${logContent || '(empty)'}`,
538
- stderrOutput ? `Stderr:\n${stderrOutput}` : '',
539
- stdoutOutput ? `Stdout:\n${stdoutOutput}` : '',
540
611
  ]
541
612
  .filter(Boolean)
542
613
  .join('\n');
@@ -575,7 +646,7 @@ export class ValkeyEngine extends BaseEngine {
575
646
  return;
576
647
  }
577
648
  // Wait for Valkey to be ready
578
- const ready = await this.waitForReady(port, version);
649
+ const ready = await this.waitForReady(name, port, version, bindAddress);
579
650
  if (ready) {
580
651
  resolve({
581
652
  port,
@@ -619,7 +690,7 @@ export class ValkeyEngine extends BaseEngine {
619
690
  }
620
691
  // Wait for Valkey to be ready to accept connections
621
692
  // TODO - consider copying the mongodb logic for this
622
- async waitForReady(port, version, timeoutMs = 60000) {
693
+ async waitForReady(containerName, port, version, bindAddress = '127.0.0.1', timeoutMs = 60000) {
623
694
  const startTime = Date.now();
624
695
  const checkInterval = 500;
625
696
  let valkeyCli;
@@ -632,10 +703,20 @@ export class ValkeyEngine extends BaseEngine {
632
703
  await new Promise((resolve) => setTimeout(resolve, 2000));
633
704
  return true;
634
705
  }
706
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
635
707
  while (Date.now() - startTime < timeoutMs) {
636
708
  try {
637
- const cmd = `"${valkeyCli}" -h 127.0.0.1 -p ${port} PING`;
638
- const { stdout } = await execAsync(cmd, { timeout: 5000 });
709
+ const auth = await this.getLocalAuth(containerName);
710
+ const args = ['-h', bindAddress, '-p', String(port)];
711
+ if (shouldPassValkeyCliUsername(auth.username)) {
712
+ args.push('--user', auth.username);
713
+ }
714
+ args.push('PING');
715
+ const { stdout } = await runValkeyCliCommand(valkeyCli, args, {
716
+ timeout: 5000,
717
+ password: auth.password,
718
+ libraryEnv,
719
+ });
639
720
  if (stdout.trim() === 'PONG') {
640
721
  logDebug(`Valkey ready on port ${port}`);
641
722
  return true;
@@ -659,10 +740,21 @@ export class ValkeyEngine extends BaseEngine {
659
740
  logDebug(`Stopping Valkey container "${name}" on port ${port}`);
660
741
  // Try graceful shutdown via valkey-cli
661
742
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
743
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
744
+ const host = getLocalCliHost(container);
662
745
  if (valkeyCli) {
663
746
  try {
664
- const cmd = `"${valkeyCli}" -h 127.0.0.1 -p ${port} SHUTDOWN SAVE`;
665
- await execAsync(cmd, { timeout: 10000 });
747
+ const auth = await this.getLocalAuth(name);
748
+ const args = ['-h', host, '-p', String(port)];
749
+ if (shouldPassValkeyCliUsername(auth.username)) {
750
+ args.push('--user', auth.username);
751
+ }
752
+ args.push('SHUTDOWN', 'SAVE');
753
+ await runValkeyCliCommand(valkeyCli, args, {
754
+ timeout: 10000,
755
+ password: auth.password,
756
+ libraryEnv,
757
+ });
666
758
  logDebug('Valkey shutdown command sent');
667
759
  // Wait a bit for process to exit
668
760
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -716,10 +808,21 @@ export class ValkeyEngine extends BaseEngine {
716
808
  const pidFile = join(containerDir, 'valkey.pid');
717
809
  // Try pinging with valkey-cli
718
810
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
811
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
812
+ const host = getLocalCliHost(container);
719
813
  if (valkeyCli) {
720
814
  try {
721
- const cmd = `"${valkeyCli}" -h 127.0.0.1 -p ${port} PING`;
722
- const { stdout } = await execAsync(cmd, { timeout: 5000 });
815
+ const auth = await this.getLocalAuth(name);
816
+ const args = ['-h', host, '-p', String(port)];
817
+ if (shouldPassValkeyCliUsername(auth.username)) {
818
+ args.push('--user', auth.username);
819
+ }
820
+ args.push('PING');
821
+ const { stdout } = await runValkeyCliCommand(valkeyCli, args, {
822
+ timeout: 5000,
823
+ password: auth.password,
824
+ libraryEnv,
825
+ });
723
826
  if (stdout.trim() === 'PONG') {
724
827
  return { running: true, message: 'Valkey is running' };
725
828
  }
@@ -785,14 +888,22 @@ export class ValkeyEngine extends BaseEngine {
785
888
  }
786
889
  // Open valkey-cli interactive shell
787
890
  async connect(container, database) {
788
- const { port, version } = container;
891
+ const { name, port, version } = container;
789
892
  const db = database || container.database || '0';
790
893
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
894
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
895
+ const auth = await this.getLocalAuth(name);
896
+ const host = getLocalCliHost(container);
897
+ const args = ['-h', host, '-p', String(port), '-n', db];
898
+ if (shouldPassValkeyCliUsername(auth.username)) {
899
+ args.push('--user', auth.username);
900
+ }
791
901
  const spawnOptions = {
792
902
  stdio: 'inherit',
903
+ env: buildValkeyCliEnv(libraryEnv, auth.password),
793
904
  };
794
905
  return new Promise((resolve, reject) => {
795
- const proc = spawn(valkeyCli, ['-h', '127.0.0.1', '-p', String(port), '-n', db], spawnOptions);
906
+ const proc = spawn(valkeyCli, args, spawnOptions);
796
907
  proc.on('error', reject);
797
908
  proc.on('close', () => resolve());
798
909
  });
@@ -816,6 +927,7 @@ export class ValkeyEngine extends BaseEngine {
816
927
  async connectWithIredis(container, database) {
817
928
  const { port } = container;
818
929
  const db = database || container.database || '0';
930
+ const host = getLocalCliHost(container);
819
931
  const iredis = await this.getIredisPath();
820
932
  if (!iredis) {
821
933
  throw new Error('iredis not found. Install it with:\n' +
@@ -826,7 +938,7 @@ export class ValkeyEngine extends BaseEngine {
826
938
  stdio: 'inherit',
827
939
  };
828
940
  return new Promise((resolve, reject) => {
829
- const proc = spawn(iredis, ['-h', '127.0.0.1', '-p', String(port), '-n', db], spawnOptions);
941
+ const proc = spawn(iredis, ['-h', host, '-p', String(port), '-n', db], spawnOptions);
830
942
  proc.on('error', reject);
831
943
  proc.on('close', () => resolve());
832
944
  });
@@ -849,16 +961,27 @@ export class ValkeyEngine extends BaseEngine {
849
961
  * Uses FLUSHDB to clear all keys in the specified database
850
962
  */
851
963
  async dropDatabase(container, database) {
852
- const { port, version } = container;
964
+ const { name, port, version } = container;
853
965
  const dbNum = parseInt(database, 10);
854
966
  if (isNaN(dbNum) || dbNum < 0 || dbNum > 15) {
855
967
  throw new Error(`Invalid Valkey database number: ${database}. Must be 0-15.`);
856
968
  }
857
969
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
970
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
971
+ const auth = await this.getLocalAuth(name);
972
+ const host = getLocalCliHost(container);
858
973
  // SELECT the database and FLUSHDB
859
- const cmd = `"${valkeyCli}" -h 127.0.0.1 -p ${port} -n ${database} FLUSHDB`;
974
+ const args = ['-h', host, '-p', String(port), '-n', database];
975
+ if (shouldPassValkeyCliUsername(auth.username)) {
976
+ args.push('--user', auth.username);
977
+ }
978
+ args.push('FLUSHDB');
860
979
  try {
861
- await execAsync(cmd, { timeout: 10000 });
980
+ await runValkeyCliCommand(valkeyCli, args, {
981
+ timeout: 10000,
982
+ password: auth.password,
983
+ libraryEnv,
984
+ });
862
985
  logDebug(`Flushed Valkey database ${database}`);
863
986
  }
864
987
  catch (error) {
@@ -876,12 +999,23 @@ export class ValkeyEngine extends BaseEngine {
876
999
  * This is acceptable for SpinDB since each container runs one Valkey server.
877
1000
  */
878
1001
  async getDatabaseSize(container) {
879
- const { port, version } = container;
1002
+ const { name, port, version } = container;
880
1003
  try {
881
1004
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
1005
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
1006
+ const auth = await this.getLocalAuth(name);
1007
+ const host = getLocalCliHost(container);
882
1008
  // INFO memory returns server-wide stats (database selection has no effect)
883
- const cmd = `"${valkeyCli}" -h 127.0.0.1 -p ${port} INFO memory`;
884
- const { stdout } = await execAsync(cmd, { timeout: 10000 });
1009
+ const args = ['-h', host, '-p', String(port)];
1010
+ if (shouldPassValkeyCliUsername(auth.username)) {
1011
+ args.push('--user', auth.username);
1012
+ }
1013
+ args.push('INFO', 'memory');
1014
+ const { stdout } = await runValkeyCliCommand(valkeyCli, args, {
1015
+ timeout: 10000,
1016
+ password: auth.password,
1017
+ libraryEnv,
1018
+ });
885
1019
  // Parse used_memory (total server memory) from INFO output
886
1020
  const match = stdout.match(/used_memory:(\d+)/);
887
1021
  if (match) {
@@ -1136,16 +1270,24 @@ export class ValkeyEngine extends BaseEngine {
1136
1270
  }
1137
1271
  // Run a Valkey command file or inline command
1138
1272
  async runScript(container, options) {
1139
- const { port, version } = container;
1273
+ const { name, port, version } = container;
1140
1274
  const db = options.database || container.database || '0';
1141
1275
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
1276
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
1277
+ const auth = await this.getLocalAuth(name);
1278
+ const host = getLocalCliHost(container);
1279
+ const args = ['-h', host, '-p', String(port), '-n', db];
1280
+ if (shouldPassValkeyCliUsername(auth.username)) {
1281
+ args.push('--user', auth.username);
1282
+ }
1283
+ const env = buildValkeyCliEnv(libraryEnv, auth.password);
1142
1284
  if (options.file) {
1143
1285
  // Read file and pipe to valkey-cli via stdin (avoids shell interpolation issues)
1144
1286
  const fileContent = await readFile(options.file, 'utf-8');
1145
- const args = ['-h', '127.0.0.1', '-p', String(port), '-n', db];
1146
1287
  await new Promise((resolve, reject) => {
1147
1288
  const proc = spawn(valkeyCli, args, {
1148
1289
  stdio: ['pipe', 'inherit', 'inherit'],
1290
+ env,
1149
1291
  });
1150
1292
  let rejected = false;
1151
1293
  proc.on('error', (err) => {
@@ -1169,10 +1311,10 @@ export class ValkeyEngine extends BaseEngine {
1169
1311
  }
1170
1312
  else if (options.sql) {
1171
1313
  // Run inline command by piping to valkey-cli stdin (avoids shell quoting issues on Windows)
1172
- const args = ['-h', '127.0.0.1', '-p', String(port), '-n', db];
1173
1314
  await new Promise((resolve, reject) => {
1174
1315
  const proc = spawn(valkeyCli, args, {
1175
1316
  stdio: ['pipe', 'inherit', 'inherit'],
1317
+ env,
1176
1318
  });
1177
1319
  let rejected = false;
1178
1320
  proc.on('error', (err) => {
@@ -1199,28 +1341,26 @@ export class ValkeyEngine extends BaseEngine {
1199
1341
  }
1200
1342
  }
1201
1343
  async executeQuery(container, query, options) {
1202
- const { port, version } = container;
1344
+ const { name, port, version } = container;
1203
1345
  const db = options?.database || container.database || '0';
1204
1346
  const host = options?.host ?? '127.0.0.1';
1205
1347
  const valkeyCli = await this.getValkeyCliPathForVersion(version);
1206
- const binBaseDir = this.getBinaryPath(version);
1207
- const libraryEnv = getLibraryEnv(binBaseDir);
1348
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
1349
+ const localAuth = !options?.username &&
1350
+ !options?.password &&
1351
+ (host === '127.0.0.1' || host === 'localhost')
1352
+ ? await this.getLocalAuth(name)
1353
+ : null;
1208
1354
  return new Promise((resolve, reject) => {
1209
1355
  const args = ['-h', host, '-p', String(port), '-n', db, '--raw'];
1210
- if (options?.username) {
1211
- args.push('--user', options.username);
1356
+ const username = options?.username || localAuth?.username;
1357
+ if (shouldPassValkeyCliUsername(username)) {
1358
+ args.push('--user', username);
1212
1359
  }
1213
1360
  if (options?.ssl) {
1214
1361
  args.push('--tls');
1215
1362
  }
1216
- // Pass password via REDISCLI_AUTH env to avoid exposing it in process listings
1217
- const env = {
1218
- ...process.env,
1219
- ...libraryEnv,
1220
- };
1221
- if (options?.password) {
1222
- env.REDISCLI_AUTH = options.password;
1223
- }
1363
+ const env = buildValkeyCliEnv(libraryEnv, options?.password ?? localAuth?.password);
1224
1364
  const proc = spawn(valkeyCli, args, {
1225
1365
  stdio: ['pipe', 'pipe', 'pipe'],
1226
1366
  env,
@@ -1262,9 +1402,12 @@ export class ValkeyEngine extends BaseEngine {
1262
1402
  async createUser(container, options) {
1263
1403
  const { username, password } = options;
1264
1404
  assertValidUsername(username);
1265
- const { port, version } = container;
1405
+ const { name, port, version } = container;
1266
1406
  const db = options.database ?? container.database ?? '0';
1267
1407
  const valkeyCli = await this.getValkeyCliPath(version);
1408
+ const libraryEnv = getResolvedValkeyCliLibraryEnv(valkeyCli);
1409
+ const auth = await this.getLocalAuth(name);
1410
+ const host = getLocalCliHost(container);
1268
1411
  // Reject passwords with characters that break ACL SETUSER syntax:
1269
1412
  // '>' sets password, '#' sets hash, '<' removes password — all are ACL delimiters.
1270
1413
  // Whitespace and newlines would split the command unexpectedly.
@@ -1273,10 +1416,14 @@ export class ValkeyEngine extends BaseEngine {
1273
1416
  }
1274
1417
  // ACL SETUSER is idempotent - sets user with full access
1275
1418
  // Send ACL command via stdin to avoid leaking password in process argv
1276
- const cliArgs = ['-h', '127.0.0.1', '-p', String(port), '-n', db];
1419
+ const cliArgs = ['-h', host, '-p', String(port), '-n', db];
1420
+ if (shouldPassValkeyCliUsername(auth.username)) {
1421
+ cliArgs.push('--user', auth.username);
1422
+ }
1277
1423
  await new Promise((resolve, reject) => {
1278
1424
  const proc = spawn(valkeyCli, cliArgs, {
1279
1425
  stdio: ['pipe', 'pipe', 'pipe'],
1426
+ env: buildValkeyCliEnv(libraryEnv, auth.password),
1280
1427
  });
1281
1428
  let stderr = '';
1282
1429
  proc.stderr?.on('data', (data) => {
@@ -1294,8 +1441,8 @@ export class ValkeyEngine extends BaseEngine {
1294
1441
  });
1295
1442
  logDebug(`Created Valkey user: ${username}`);
1296
1443
  // Valkey uses redis:// scheme for compatibility
1297
- const connectionString = `redis://${encodeURIComponent(username)}:${encodeURIComponent(password)}@127.0.0.1:${port}/${db}`;
1298
- return {
1444
+ const connectionString = `redis://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}/${db}`;
1445
+ const credentials = {
1299
1446
  username,
1300
1447
  password,
1301
1448
  connectionString,
@@ -1303,6 +1450,8 @@ export class ValkeyEngine extends BaseEngine {
1303
1450
  container: container.name,
1304
1451
  database: db,
1305
1452
  };
1453
+ await saveCredentials(name, Engine.Valkey, credentials);
1454
+ return credentials;
1306
1455
  }
1307
1456
  }
1308
1457
  export const valkeyEngine = new ValkeyEngine();