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.
- package/dist/cli/commands/info.js +12 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/link.js +6 -0
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/list.js +4 -1
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/menu/container-handlers.js +25 -7
- package/dist/cli/commands/menu/container-handlers.js.map +1 -1
- package/dist/config/backup-formats.js +11 -11
- package/dist/config/backup-formats.js.map +1 -1
- package/dist/config/version.js +1 -1
- package/dist/core/credential-manager.js +34 -11
- package/dist/core/credential-manager.js.map +1 -1
- package/dist/core/query-parser.js +55 -1
- package/dist/core/query-parser.js.map +1 -1
- package/dist/core/remote-container.js +11 -0
- package/dist/core/remote-container.js.map +1 -1
- package/dist/engines/clickhouse/backup.js +42 -10
- package/dist/engines/clickhouse/backup.js.map +1 -1
- package/dist/engines/clickhouse/restore.js +41 -10
- package/dist/engines/clickhouse/restore.js.map +1 -1
- package/dist/engines/cockroachdb/backup.js +18 -22
- package/dist/engines/cockroachdb/backup.js.map +1 -1
- package/dist/engines/cockroachdb/cli-utils.js +66 -0
- package/dist/engines/cockroachdb/cli-utils.js.map +1 -1
- package/dist/engines/cockroachdb/index.js +199 -116
- package/dist/engines/cockroachdb/index.js.map +1 -1
- package/dist/engines/cockroachdb/restore.js +19 -26
- package/dist/engines/cockroachdb/restore.js.map +1 -1
- package/dist/engines/couchdb/backup.js +13 -4
- package/dist/engines/couchdb/backup.js.map +1 -1
- package/dist/engines/couchdb/index.js +93 -25
- package/dist/engines/couchdb/index.js.map +1 -1
- package/dist/engines/couchdb/restore.js +15 -4
- package/dist/engines/couchdb/restore.js.map +1 -1
- package/dist/engines/ferretdb/backup.js +88 -91
- package/dist/engines/ferretdb/backup.js.map +1 -1
- package/dist/engines/ferretdb/index.js +179 -226
- package/dist/engines/ferretdb/index.js.map +1 -1
- package/dist/engines/ferretdb/restore.js +223 -20
- package/dist/engines/ferretdb/restore.js.map +1 -1
- package/dist/engines/influxdb/api-client.js +1 -1
- package/dist/engines/influxdb/api-client.js.map +1 -1
- package/dist/engines/influxdb/backup.js +25 -5
- package/dist/engines/influxdb/backup.js.map +1 -1
- package/dist/engines/influxdb/index.js +165 -43
- package/dist/engines/influxdb/index.js.map +1 -1
- package/dist/engines/influxdb/restore.js +22 -2
- package/dist/engines/influxdb/restore.js.map +1 -1
- package/dist/engines/mariadb/backup.js +24 -15
- package/dist/engines/mariadb/backup.js.map +1 -1
- package/dist/engines/mariadb/env.js +11 -0
- package/dist/engines/mariadb/env.js.map +1 -0
- package/dist/engines/mariadb/index.js +192 -113
- package/dist/engines/mariadb/index.js.map +1 -1
- package/dist/engines/mariadb/restore.js +21 -5
- package/dist/engines/mariadb/restore.js.map +1 -1
- package/dist/engines/meilisearch/backup.js +8 -4
- package/dist/engines/meilisearch/backup.js.map +1 -1
- package/dist/engines/meilisearch/index.js +55 -58
- package/dist/engines/meilisearch/index.js.map +1 -1
- package/dist/engines/mongo-uri.js +8 -0
- package/dist/engines/mongo-uri.js.map +1 -0
- package/dist/engines/mongodb/backup.js +62 -13
- package/dist/engines/mongodb/backup.js.map +1 -1
- package/dist/engines/mongodb/index.js +170 -108
- package/dist/engines/mongodb/index.js.map +1 -1
- package/dist/engines/mongodb/restore.js +21 -1
- package/dist/engines/mongodb/restore.js.map +1 -1
- package/dist/engines/mysql/backup.js +24 -7
- package/dist/engines/mysql/backup.js.map +1 -1
- package/dist/engines/mysql/index.js +154 -89
- package/dist/engines/mysql/index.js.map +1 -1
- package/dist/engines/mysql/restore.js +14 -4
- package/dist/engines/mysql/restore.js.map +1 -1
- package/dist/engines/postgresql/backup.js +9 -2
- package/dist/engines/postgresql/backup.js.map +1 -1
- package/dist/engines/postgresql/index.js +10 -4
- package/dist/engines/postgresql/index.js.map +1 -1
- package/dist/engines/postgresql/restore.js +7 -3
- package/dist/engines/postgresql/restore.js.map +1 -1
- package/dist/engines/qdrant/backup.js +5 -1
- package/dist/engines/qdrant/backup.js.map +1 -1
- package/dist/engines/qdrant/index.js +31 -2
- package/dist/engines/qdrant/index.js.map +1 -1
- package/dist/engines/qdrant/restore.js +5 -3
- package/dist/engines/qdrant/restore.js.map +1 -1
- package/dist/engines/questdb/auth.js +26 -0
- package/dist/engines/questdb/auth.js.map +1 -0
- package/dist/engines/questdb/backup.js +10 -8
- package/dist/engines/questdb/backup.js.map +1 -1
- package/dist/engines/questdb/index.js +16 -8
- package/dist/engines/questdb/index.js.map +1 -1
- package/dist/engines/questdb/restore.js +7 -5
- package/dist/engines/questdb/restore.js.map +1 -1
- package/dist/engines/redis/backup.js +48 -15
- package/dist/engines/redis/backup.js.map +1 -1
- package/dist/engines/redis/index.js +45 -12
- package/dist/engines/redis/index.js.map +1 -1
- package/dist/engines/redis/restore.js +21 -2
- package/dist/engines/redis/restore.js.map +1 -1
- package/dist/engines/surrealdb/auth.js +98 -0
- package/dist/engines/surrealdb/auth.js.map +1 -0
- package/dist/engines/surrealdb/backup.js +72 -9
- package/dist/engines/surrealdb/backup.js.map +1 -1
- package/dist/engines/surrealdb/index.js +84 -144
- package/dist/engines/surrealdb/index.js.map +1 -1
- package/dist/engines/surrealdb/restore.js +32 -31
- package/dist/engines/surrealdb/restore.js.map +1 -1
- package/dist/engines/typedb/backup.js +9 -1
- package/dist/engines/typedb/backup.js.map +1 -1
- package/dist/engines/typedb/cli-utils.js +3 -3
- package/dist/engines/typedb/cli-utils.js.map +1 -1
- package/dist/engines/typedb/index.js +9 -2
- package/dist/engines/typedb/index.js.map +1 -1
- package/dist/engines/typedb/restore.js +19 -7
- package/dist/engines/typedb/restore.js.map +1 -1
- package/dist/engines/valkey/backup.js +37 -13
- package/dist/engines/valkey/backup.js.map +1 -1
- package/dist/engines/valkey/index.js +207 -58
- package/dist/engines/valkey/index.js.map +1 -1
- package/dist/engines/valkey/restore.js +21 -2
- package/dist/engines/valkey/restore.js.map +1 -1
- package/dist/engines/weaviate/backup.js +7 -2
- package/dist/engines/weaviate/backup.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { spawn
|
|
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', '
|
|
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(
|
|
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
|
|
638
|
-
const
|
|
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
|
|
665
|
-
|
|
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
|
|
722
|
-
const
|
|
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,
|
|
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',
|
|
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
|
|
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
|
|
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
|
|
884
|
-
|
|
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
|
|
1207
|
-
const
|
|
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
|
-
|
|
1211
|
-
|
|
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
|
-
|
|
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',
|
|
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)}
|
|
1298
|
-
|
|
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();
|