falkordb 6.8.0 → 6.9.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/index.d.ts +3 -2
- package/dist/index.js +8 -5
- package/dist/src/clients/client.d.ts +5 -13
- package/dist/src/clients/cluster.d.ts +7 -12
- package/dist/src/clients/cluster.js +38 -5
- package/dist/src/clients/nullClient.d.ts +3 -11
- package/dist/src/clients/sentinel.d.ts +31 -0
- package/dist/src/clients/sentinel.js +179 -24
- package/dist/src/clients/single.d.ts +16 -12
- package/dist/src/clients/single.js +29 -21
- package/dist/src/commands/CONFIG_GET.d.ts +1 -5
- package/dist/src/commands/CONSTRAINT_CREATE.d.ts +2 -8
- package/dist/src/commands/CONSTRAINT_CREATE.js +3 -10
- package/dist/src/commands/CONSTRAINT_DROP.d.ts +1 -1
- package/dist/src/commands/MEMORY_USAGE.d.ts +2 -4
- package/dist/src/commands/QUERY.d.ts +4 -23
- package/dist/src/commands/QUERY.js +14 -4
- package/dist/src/commands/RO_QUERY.d.ts +2 -2
- package/dist/src/commands/RO_QUERY.js +4 -4
- package/dist/src/commands/SLOWLOG.d.ts +1 -13
- package/dist/src/commands/UDF_LIST.d.ts +2 -2
- package/dist/src/commands/index.d.ts +4 -11
- package/dist/src/commands/index.js +58 -22
- package/dist/src/enums/constraint-type.d.ts +4 -0
- package/dist/src/enums/constraint-type.js +8 -0
- package/dist/src/enums/entity-type.d.ts +4 -0
- package/dist/src/enums/entity-type.js +8 -0
- package/dist/src/enums/graph-value-types.d.ts +19 -0
- package/dist/src/enums/graph-value-types.js +24 -0
- package/dist/src/enums/index.d.ts +3 -0
- package/dist/src/enums/index.js +19 -0
- package/dist/src/errors.d.ts +10 -0
- package/dist/src/errors.js +18 -0
- package/dist/src/falkordb.d.ts +13 -88
- package/dist/src/falkordb.js +131 -8
- package/dist/src/graph.d.ts +27 -15
- package/dist/src/graph.js +90 -52
- package/dist/src/types/cluster-graph-connection.d.ts +5 -0
- package/dist/src/types/cluster-graph-connection.js +2 -0
- package/dist/src/types/config-item.d.ts +4 -0
- package/dist/src/types/config-item.js +2 -0
- package/dist/src/types/falkordb-options.d.ts +65 -0
- package/dist/src/types/falkordb-options.js +2 -0
- package/dist/src/types/graph-metadata.d.ts +5 -0
- package/dist/src/types/graph-metadata.js +2 -0
- package/dist/src/types/graph-raw-value.d.ts +29 -0
- package/dist/src/types/graph-raw-value.js +2 -0
- package/dist/src/types/graph-value.d.ts +29 -0
- package/dist/src/types/graph-value.js +2 -0
- package/dist/src/types/index.d.ts +16 -0
- package/dist/src/types/index.js +32 -0
- package/dist/src/types/memory-usage.d.ts +4 -0
- package/dist/src/types/memory-usage.js +2 -0
- package/dist/src/types/net-socket-options.d.ts +4 -0
- package/dist/src/types/net-socket-options.js +2 -0
- package/dist/src/types/query-options.d.ts +9 -0
- package/dist/src/types/query-options.js +2 -0
- package/dist/src/types/query-reply.d.ts +20 -0
- package/dist/src/types/query-reply.js +2 -0
- package/dist/src/types/single-graph-connection.d.ts +5 -0
- package/dist/src/types/single-graph-connection.js +2 -0
- package/dist/src/types/slow-log.d.ts +12 -0
- package/dist/src/types/slow-log.js +2 -0
- package/dist/src/types/socket-options.d.ts +40 -0
- package/dist/src/types/socket-options.js +2 -0
- package/dist/src/types/tls-socket-options.d.ts +4 -0
- package/dist/src/types/tls-socket-options.js +2 -0
- package/dist/src/types/typed-redis-client-options.d.ts +9 -0
- package/dist/src/types/typed-redis-client-options.js +2 -0
- package/dist/src/types/udf-list.d.ts +2 -0
- package/dist/src/types/udf-list.js +2 -0
- package/package.json +7 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as Graph } from './src/graph';
|
|
2
|
-
export { ConstraintType, EntityType } from './src/
|
|
2
|
+
export { ConstraintType, EntityType } from './src/enums';
|
|
3
3
|
export { default as FalkorDB } from './src/falkordb';
|
|
4
|
-
export type { FalkorDBOptions } from './src/
|
|
4
|
+
export type { FalkorDBOptions } from './src/types';
|
|
5
|
+
export type { QueryParam, QueryParams, QueryOptions, QueryOptionsBackwardCompatible } from './src/types';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.FalkorDB = exports.EntityType = exports.ConstraintType = exports.Graph = void 0;
|
|
4
7
|
var graph_1 = require("./src/graph");
|
|
5
|
-
Object.defineProperty(exports, "Graph", { enumerable: true, get: function () { return graph_1.default; } });
|
|
6
|
-
var
|
|
7
|
-
Object.defineProperty(exports, "ConstraintType", { enumerable: true, get: function () { return
|
|
8
|
-
Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return
|
|
8
|
+
Object.defineProperty(exports, "Graph", { enumerable: true, get: function () { return __importDefault(graph_1).default; } });
|
|
9
|
+
var enums_1 = require("./src/enums");
|
|
10
|
+
Object.defineProperty(exports, "ConstraintType", { enumerable: true, get: function () { return enums_1.ConstraintType; } });
|
|
11
|
+
Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return enums_1.EntityType; } });
|
|
9
12
|
var falkordb_1 = require("./src/falkordb");
|
|
10
|
-
Object.defineProperty(exports, "FalkorDB", { enumerable: true, get: function () { return falkordb_1.default; } });
|
|
13
|
+
Object.defineProperty(exports, "FalkorDB", { enumerable: true, get: function () { return __importDefault(falkordb_1).default; } });
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { RedisArgument } from "@redis/client";
|
|
2
|
-
import {
|
|
3
|
-
import { ConstraintType, EntityType } from "../graph";
|
|
2
|
+
import { ConstraintType, EntityType } from "../enums";
|
|
4
3
|
import FalkorDB from "../falkordb";
|
|
5
|
-
import { SingleGraphConnection } from "
|
|
6
|
-
import { MemoryUsageOptions, MemoryUsageReply } from "../commands/MEMORY_USAGE";
|
|
7
|
-
import { UdfListReply } from "../commands/UDF_LIST";
|
|
4
|
+
import type { MemoryUsageOptions, MemoryUsageReply, QueryOptions, SingleGraphConnection, SlowLogReply, UdfListReply } from "../types";
|
|
8
5
|
export interface Client {
|
|
9
6
|
init(falkordb: FalkorDB): Promise<void>;
|
|
10
7
|
list(): Promise<Array<string>>;
|
|
@@ -19,19 +16,14 @@ export interface Client {
|
|
|
19
16
|
configGet(configKey: string): Promise<(string | number)[] | (string | number)[][]>;
|
|
20
17
|
configSet(configKey: string, value: number | string): Promise<void>;
|
|
21
18
|
info(section?: string): Promise<(string | string[])[]>;
|
|
22
|
-
query<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean): Promise<any>;
|
|
19
|
+
query<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean, version?: number): Promise<any>;
|
|
23
20
|
profile<T>(graph: string, query: RedisArgument): Promise<any>;
|
|
24
|
-
roQuery<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean): Promise<any>;
|
|
21
|
+
roQuery<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean, version?: number): Promise<any>;
|
|
25
22
|
copy<T>(srcGraph: string, destGraph: string): Promise<any>;
|
|
26
23
|
delete(graph: string): Promise<void>;
|
|
27
24
|
explain(graph: string, query: string): Promise<any>;
|
|
28
25
|
memoryUsage(graph: string, options?: MemoryUsageOptions): Promise<MemoryUsageReply>;
|
|
29
|
-
slowLog(graph: string): Promise<
|
|
30
|
-
timestamp: Date;
|
|
31
|
-
command: string;
|
|
32
|
-
query: string;
|
|
33
|
-
took: number;
|
|
34
|
-
}[]>;
|
|
26
|
+
slowLog(graph: string): Promise<SlowLogReply>;
|
|
35
27
|
constraintCreate(graph: string, constraintType: ConstraintType, entityType: EntityType, label: string, ...properties: string[]): Promise<void>;
|
|
36
28
|
constraintDrop(graph: string, constraintType: ConstraintType, entityType: EntityType, label: string, ...properties: string[]): Promise<void>;
|
|
37
29
|
udfLoad(name: string, script: string | Function, replace?: boolean): Promise<string>;
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { Client } from "./client";
|
|
2
|
-
import { ConstraintType, EntityType } from "../
|
|
2
|
+
import { ConstraintType, EntityType } from "../enums";
|
|
3
3
|
import { RedisArgument, RedisFunctions, RedisScripts } from "@redis/client";
|
|
4
|
-
import commands
|
|
5
|
-
import { RedisClusterType } from "@redis/client";
|
|
4
|
+
import commands from "../commands";
|
|
6
5
|
import FalkorDB from "../falkordb";
|
|
7
|
-
import { SingleGraphConnection } from "
|
|
8
|
-
|
|
9
|
-
import { UdfListReply } from "../commands/UDF_LIST";
|
|
10
|
-
export type ClusterGraphConnection = RedisClusterType<{
|
|
11
|
-
falkordb: typeof commands;
|
|
12
|
-
}, RedisFunctions, RedisScripts, 2>;
|
|
6
|
+
import type { ClusterGraphConnection, MemoryUsageOptions, MemoryUsageReply, QueryOptions, SingleGraphConnection, UdfListReply } from "../types";
|
|
7
|
+
export type { ClusterGraphConnection };
|
|
13
8
|
/**
|
|
14
9
|
* A client that connects to a Redis Cluster.
|
|
15
10
|
*/
|
|
@@ -20,7 +15,7 @@ export declare class Cluster implements Client {
|
|
|
20
15
|
falkordb: typeof commands;
|
|
21
16
|
}, RedisFunctions, RedisScripts, 2, {}>>;
|
|
22
17
|
init(falkordb: FalkorDB): Promise<void>;
|
|
23
|
-
query<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean): Promise<{
|
|
18
|
+
query<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean, version?: number): Promise<{
|
|
24
19
|
headers: undefined;
|
|
25
20
|
data: undefined;
|
|
26
21
|
metadata: string[];
|
|
@@ -29,7 +24,7 @@ export declare class Cluster implements Client {
|
|
|
29
24
|
data: (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[];
|
|
30
25
|
metadata: string[];
|
|
31
26
|
}>;
|
|
32
|
-
roQuery<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean): Promise<{
|
|
27
|
+
roQuery<T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean, version?: number): Promise<{
|
|
33
28
|
headers: undefined;
|
|
34
29
|
data: undefined;
|
|
35
30
|
metadata: string[];
|
|
@@ -42,7 +37,7 @@ export declare class Cluster implements Client {
|
|
|
42
37
|
explain(graph: string, query: string): Promise<string[]>;
|
|
43
38
|
list(): Promise<Array<string>>;
|
|
44
39
|
stubs(): Promise<Array<string>>;
|
|
45
|
-
configGet(configKey: string): Promise<(string | number)[] | (string | number)[]
|
|
40
|
+
configGet(configKey: string): Promise<(string | number)[][] | (string | number)[]>;
|
|
46
41
|
configSet(configKey: string, value: number | string): Promise<void>;
|
|
47
42
|
info(section?: string): Promise<(string | string[])[]>;
|
|
48
43
|
copy<_T>(srcGraph: string, destGraph: string): Promise<"OK">;
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.Cluster = void 0;
|
|
4
37
|
const client_1 = require("@redis/client");
|
|
5
|
-
const lodash = require("lodash");
|
|
38
|
+
const lodash = __importStar(require("lodash"));
|
|
6
39
|
/**
|
|
7
40
|
* A client that connects to a Redis Cluster.
|
|
8
41
|
*/
|
|
@@ -32,12 +65,12 @@ class Cluster {
|
|
|
32
65
|
.connect();
|
|
33
66
|
}
|
|
34
67
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
35
|
-
async query(graph, query, options, compact = true) {
|
|
36
|
-
return this.#client.falkordb.query(graph, query, options, compact);
|
|
68
|
+
async query(graph, query, options, compact = true, version) {
|
|
69
|
+
return this.#client.falkordb.query(graph, query, options, compact, version);
|
|
37
70
|
}
|
|
38
71
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
39
|
-
async roQuery(graph, query, options, compact = true) {
|
|
40
|
-
return this.#client.falkordb.roQuery(graph, query, options, compact);
|
|
72
|
+
async roQuery(graph, query, options, compact = true, version) {
|
|
73
|
+
return this.#client.falkordb.roQuery(graph, query, options, compact, version);
|
|
41
74
|
}
|
|
42
75
|
async delete(graph) {
|
|
43
76
|
const reply = this.#client.falkordb.delete(graph);
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { RedisArgument } from "@redis/client";
|
|
2
|
-
import { QueryOptions } from "../commands";
|
|
3
2
|
import FalkorDB from "../falkordb";
|
|
4
|
-
import { ConstraintType, EntityType } from "../
|
|
3
|
+
import { ConstraintType, EntityType } from "../enums";
|
|
5
4
|
import { Client } from "./client";
|
|
6
|
-
import { SingleGraphConnection } from "
|
|
7
|
-
import { MemoryUsageOptions, MemoryUsageReply } from "../commands/MEMORY_USAGE";
|
|
8
|
-
import { UdfListReply } from "../commands/UDF_LIST";
|
|
5
|
+
import type { MemoryUsageOptions, MemoryUsageReply, QueryOptions, SingleGraphConnection, SlowLogReply, UdfListReply } from "../types";
|
|
9
6
|
/**
|
|
10
7
|
* The `NullClient` class is a placeholder implementation of the `Client` interface.
|
|
11
8
|
*
|
|
@@ -31,12 +28,7 @@ export declare class NullClient implements Client {
|
|
|
31
28
|
copy<_T>(_srcGraph: string, _destGraph: string): Promise<any>;
|
|
32
29
|
delete(_graph: string): Promise<void>;
|
|
33
30
|
explain(_graph: string, _query: string): Promise<any>;
|
|
34
|
-
slowLog(_graph: string): Promise<
|
|
35
|
-
timestamp: Date;
|
|
36
|
-
command: string;
|
|
37
|
-
query: string;
|
|
38
|
-
took: number;
|
|
39
|
-
}[]>;
|
|
31
|
+
slowLog(_graph: string): Promise<SlowLogReply>;
|
|
40
32
|
memoryUsage(_graph: string, _options?: MemoryUsageOptions): Promise<MemoryUsageReply>;
|
|
41
33
|
constraintCreate(_graph: string, _constraintType: ConstraintType, _entityType: EntityType, _label: string, ..._properties: string[]): Promise<void>;
|
|
42
34
|
constraintDrop(_graph: string, _constraintType: ConstraintType, _entityType: EntityType, _label: string, ..._properties: string[]): Promise<void>;
|
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { Single } from "./single";
|
|
2
2
|
import FalkorDB from "../falkordb";
|
|
3
|
+
import type { SingleGraphConnection } from "../types";
|
|
4
|
+
import type { RunOptions } from "./single";
|
|
5
|
+
export interface SentinelOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Resend write commands to the new master when the connection is lost while
|
|
8
|
+
* waiting for their reply. The old master may have already run them, so a
|
|
9
|
+
* write can be applied twice. Read-only commands, and commands the server
|
|
10
|
+
* certainly did not run, are always resent.
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
retryWritesOnConnectionLoss?: boolean;
|
|
14
|
+
}
|
|
3
15
|
export declare class Sentinel extends Single {
|
|
4
16
|
private sentinelClient;
|
|
17
|
+
private redisOption;
|
|
18
|
+
private falkordb?;
|
|
19
|
+
private ready;
|
|
20
|
+
private closed;
|
|
21
|
+
private recovery;
|
|
22
|
+
private readonly retryWritesOnConnectionLoss;
|
|
23
|
+
constructor(client: SingleGraphConnection, options?: SentinelOptions);
|
|
5
24
|
init(falkordb: FalkorDB): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Runs a command on the current master. If the master connection is lost, or
|
|
27
|
+
* the node was demoted, waits for the new master and resends the command
|
|
28
|
+
* there when that is safe: the command certainly did not run, it is
|
|
29
|
+
* read-only, or `retryWritesOnConnectionLoss` is set.
|
|
30
|
+
*/
|
|
31
|
+
protected run<T>(fn: (client: SingleGraphConnection) => Promise<T>, { readOnly }?: RunOptions): Promise<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Re-discovers the master and reconnects, retrying with backoff until it
|
|
34
|
+
* succeeds or the client is closed.
|
|
35
|
+
*/
|
|
36
|
+
private recover;
|
|
6
37
|
/**
|
|
7
38
|
* Connect to the server using the details from sentinel server
|
|
8
39
|
* Register error event to reconnect on error from the sentinel server
|
|
@@ -3,6 +3,78 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Sentinel = void 0;
|
|
4
4
|
const single_1 = require("./single");
|
|
5
5
|
const client_1 = require("@redis/client");
|
|
6
|
+
// How many times a command is resent after losing its connection to the master
|
|
7
|
+
const MAX_COMMAND_RETRIES = 3;
|
|
8
|
+
// Backoff between attempts to re-discover the master
|
|
9
|
+
const MIN_RECOVERY_DELAY_MS = 100;
|
|
10
|
+
const MAX_RECOVERY_DELAY_MS = 2000;
|
|
11
|
+
// How long a demoted master's connection may take to finish in-flight commands
|
|
12
|
+
const CLOSE_TIMEOUT_MS = 5000;
|
|
13
|
+
// Connections destroyed while commands may still have been in flight, whose
|
|
14
|
+
// DisconnectsClientError rejections therefore have an unknown outcome
|
|
15
|
+
const forceClosed = new WeakSet();
|
|
16
|
+
const RETRYABLE_SOCKET_CODES = new Set([
|
|
17
|
+
"ECONNRESET",
|
|
18
|
+
"ECONNREFUSED",
|
|
19
|
+
"EPIPE",
|
|
20
|
+
"ETIMEDOUT",
|
|
21
|
+
"EHOSTUNREACH",
|
|
22
|
+
"ENETUNREACH",
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Whether the command certainly did not run on the server, so it is safe to
|
|
26
|
+
* resend it to the current master.
|
|
27
|
+
*/
|
|
28
|
+
function wasNotExecuted(err, client) {
|
|
29
|
+
if (err instanceof client_1.DisconnectsClientError && forceClosed.has(client)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (err instanceof client_1.ErrorReply) {
|
|
33
|
+
// The node was demoted to a replica while the command was queued or blocked
|
|
34
|
+
return /^(READONLY|UNBLOCKED)\b/.test(err.message);
|
|
35
|
+
}
|
|
36
|
+
// Rejected before being sent: on a closed client, or flushed unsent when a
|
|
37
|
+
// dead master connection is destroyed (see dropClient)
|
|
38
|
+
return (err instanceof client_1.ClientClosedError ||
|
|
39
|
+
err instanceof client_1.ClientOfflineError ||
|
|
40
|
+
err instanceof client_1.DisconnectsClientError);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether the connection dropped while waiting for the reply, so the command
|
|
44
|
+
* may or may not have run on the server.
|
|
45
|
+
*/
|
|
46
|
+
function isConnectionLost(err, client) {
|
|
47
|
+
return (err instanceof client_1.SocketClosedUnexpectedlyError ||
|
|
48
|
+
(err instanceof client_1.DisconnectsClientError && forceClosed.has(client)) ||
|
|
49
|
+
RETRYABLE_SOCKET_CODES.has(err?.code ?? ""));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stops using a master connection. Commands still unsent are rejected with
|
|
53
|
+
* DisconnectsClientError, which run() resends.
|
|
54
|
+
*/
|
|
55
|
+
function dropClient(client) {
|
|
56
|
+
if (client.isReady) {
|
|
57
|
+
// The connection is alive (e.g. the node was demoted): let in-flight
|
|
58
|
+
// commands get their replies, so none is left with an unknown outcome.
|
|
59
|
+
// If they take too long, give up on them: their outcome is then unknown.
|
|
60
|
+
const forceClose = () => {
|
|
61
|
+
forceClosed.add(client);
|
|
62
|
+
client.destroy();
|
|
63
|
+
};
|
|
64
|
+
const timer = setTimeout(forceClose, CLOSE_TIMEOUT_MS);
|
|
65
|
+
timer.unref?.();
|
|
66
|
+
client
|
|
67
|
+
.close()
|
|
68
|
+
.catch(forceClose)
|
|
69
|
+
.finally(() => clearTimeout(timer));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// The socket is gone. Destroy once node-redis has failed the in-flight
|
|
73
|
+
// commands with the socket error, so that DisconnectsClientError only
|
|
74
|
+
// reaches commands that were never sent.
|
|
75
|
+
queueMicrotask(() => client.destroy());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
6
78
|
function extractDetails(masters) {
|
|
7
79
|
const allDetails = [];
|
|
8
80
|
for (const master of masters) {
|
|
@@ -16,15 +88,99 @@ function extractDetails(masters) {
|
|
|
16
88
|
}
|
|
17
89
|
class Sentinel extends single_1.Single {
|
|
18
90
|
sentinelClient;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
91
|
+
redisOption = {};
|
|
92
|
+
falkordb;
|
|
93
|
+
// Set once the first master connection is up; recovery only runs after that
|
|
94
|
+
ready = false;
|
|
95
|
+
closed = false;
|
|
96
|
+
// Pending re-discovery of the master, shared by every command waiting on it
|
|
97
|
+
recovery = null;
|
|
98
|
+
retryWritesOnConnectionLoss;
|
|
99
|
+
constructor(client, options = {}) {
|
|
100
|
+
super(client);
|
|
101
|
+
this.retryWritesOnConnectionLoss = options.retryWritesOnConnectionLoss ?? false;
|
|
102
|
+
}
|
|
103
|
+
async init(falkordb) {
|
|
104
|
+
this.redisOption = (this.client.options ?? {});
|
|
105
|
+
this.falkordb = falkordb;
|
|
106
|
+
await this.tryConnectSentinelServer(this.client, this.redisOption, falkordb);
|
|
107
|
+
this.ready = true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Runs a command on the current master. If the master connection is lost, or
|
|
111
|
+
* the node was demoted, waits for the new master and resends the command
|
|
112
|
+
* there when that is safe: the command certainly did not run, it is
|
|
113
|
+
* read-only, or `retryWritesOnConnectionLoss` is set.
|
|
114
|
+
*/
|
|
115
|
+
async run(fn, { readOnly = false } = {}) {
|
|
116
|
+
for (let attempt = 0;; attempt++) {
|
|
117
|
+
if (this.recovery) {
|
|
118
|
+
await this.recovery;
|
|
119
|
+
}
|
|
120
|
+
const client = this.client;
|
|
121
|
+
try {
|
|
122
|
+
return await fn(client);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const notExecuted = wasNotExecuted(err, client);
|
|
126
|
+
const connectionLost = isConnectionLost(err, client);
|
|
127
|
+
if (this.closed || (!notExecuted && !connectionLost)) {
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
// Switch to the new master even when this command is not resent
|
|
131
|
+
// e.g. READONLY: the connection is fine but points to a replica now
|
|
132
|
+
if (this.client === client) {
|
|
133
|
+
this.recover(client);
|
|
134
|
+
}
|
|
135
|
+
const resend = notExecuted || readOnly || this.retryWritesOnConnectionLoss;
|
|
136
|
+
if (!resend || attempt >= MAX_COMMAND_RETRIES) {
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
console.debug("Command failed on master connection, retrying", err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Re-discovers the master and reconnects, retrying with backoff until it
|
|
145
|
+
* succeeds or the client is closed.
|
|
146
|
+
*/
|
|
147
|
+
recover(failedClient) {
|
|
148
|
+
if (this.recovery) {
|
|
149
|
+
return this.recovery;
|
|
150
|
+
}
|
|
151
|
+
// Stop sending to the old master. Commands it rejects unsent are resent
|
|
152
|
+
// by run() once the new master is up.
|
|
153
|
+
dropClient(failedClient);
|
|
154
|
+
this.recovery = (async () => {
|
|
155
|
+
for (let attempt = 0; !this.closed; attempt++) {
|
|
156
|
+
try {
|
|
157
|
+
await this.tryConnectSentinelServer(this.sentinelClient, this.redisOption, this.falkordb);
|
|
158
|
+
console.debug("Connected to server");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
console.debug("Error on server reconnect", e);
|
|
163
|
+
// Emitting "error" without a listener throws, which would end recovery
|
|
164
|
+
if (this.falkordb?.listenerCount("error")) {
|
|
165
|
+
this.falkordb.emit("error", e);
|
|
166
|
+
}
|
|
167
|
+
const delay = Math.min(MIN_RECOVERY_DELAY_MS * 2 ** attempt, MAX_RECOVERY_DELAY_MS);
|
|
168
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
throw new client_1.ClientClosedError();
|
|
172
|
+
})().finally(() => {
|
|
173
|
+
this.recovery = null;
|
|
174
|
+
});
|
|
175
|
+
// Avoid an unhandled rejection when no command is waiting on it
|
|
176
|
+
this.recovery.catch(() => { });
|
|
177
|
+
return this.recovery;
|
|
22
178
|
}
|
|
23
179
|
/**
|
|
24
180
|
* Connect to the server using the details from sentinel server
|
|
25
181
|
* Register error event to reconnect on error from the sentinel server
|
|
26
182
|
*/
|
|
27
|
-
async tryConnectSentinelServer(client, redisOption,
|
|
183
|
+
async tryConnectSentinelServer(client, redisOption, _falkordb) {
|
|
28
184
|
// TODO support multi sentinels
|
|
29
185
|
const masters = await client.falkordb.sentinelMasters();
|
|
30
186
|
const details = extractDetails(masters);
|
|
@@ -46,31 +202,30 @@ class Sentinel extends single_1.Single {
|
|
|
46
202
|
this.sentinelClient = client;
|
|
47
203
|
// Set original client as sentinel and server client as client
|
|
48
204
|
this.client = realClient;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
68
|
-
.connect();
|
|
205
|
+
try {
|
|
206
|
+
await realClient
|
|
207
|
+
.on("error", (err) => {
|
|
208
|
+
console.debug("Error on server connection", err);
|
|
209
|
+
// Errors on a previous server connection, or before the first
|
|
210
|
+
// connection is up (init() reports those), need no recovery
|
|
211
|
+
if (this.client !== realClient || !this.ready || this.recovery) {
|
|
212
|
+
dropClient(realClient);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
this.recover(realClient);
|
|
216
|
+
})
|
|
217
|
+
.connect();
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
realClient.destroy();
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
69
223
|
}
|
|
70
224
|
quit() {
|
|
71
225
|
return this.disconnect();
|
|
72
226
|
}
|
|
73
227
|
async disconnect() {
|
|
228
|
+
this.closed = true;
|
|
74
229
|
const promises = [super.disconnect()];
|
|
75
230
|
if (this.sentinelClient) {
|
|
76
231
|
const reply = this.sentinelClient.disconnect();
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { Client } from "./client";
|
|
2
|
-
import { ConstraintType, EntityType } from "../
|
|
3
|
-
import { RedisArgument
|
|
4
|
-
import commands, { QueryOptions } from "../commands";
|
|
5
|
-
import { RedisClientType } from "@redis/client";
|
|
2
|
+
import { ConstraintType, EntityType } from "../enums";
|
|
3
|
+
import { RedisArgument } from "@redis/client";
|
|
6
4
|
import FalkorDB from "../falkordb";
|
|
7
|
-
import { MemoryUsageOptions, MemoryUsageReply } from "../
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
import type { MemoryUsageOptions, MemoryUsageReply, QueryOptions, SingleGraphConnection, UdfListReply } from "../types";
|
|
6
|
+
export type { SingleGraphConnection };
|
|
7
|
+
export interface RunOptions {
|
|
8
|
+
/** The command does not change data, so running it twice is harmless */
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
}
|
|
12
11
|
export declare class Single implements Client {
|
|
13
12
|
protected client: SingleGraphConnection;
|
|
14
13
|
constructor(client: SingleGraphConnection);
|
|
15
14
|
init(_falkordb: FalkorDB): Promise<void>;
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Runs a command against the underlying connection.
|
|
17
|
+
* Subclasses override it to add recovery, e.g. retrying on a new master.
|
|
18
|
+
*/
|
|
19
|
+
protected run<T>(fn: (client: SingleGraphConnection) => Promise<T>, _options?: RunOptions): Promise<T>;
|
|
20
|
+
query<_T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean, version?: number): Promise<{
|
|
17
21
|
headers: undefined;
|
|
18
22
|
data: undefined;
|
|
19
23
|
metadata: string[];
|
|
@@ -22,7 +26,7 @@ export declare class Single implements Client {
|
|
|
22
26
|
data: (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | (string | number | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[];
|
|
23
27
|
metadata: string[];
|
|
24
28
|
}>;
|
|
25
|
-
roQuery<_T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean): Promise<{
|
|
29
|
+
roQuery<_T>(graph: string, query: RedisArgument, options?: QueryOptions, compact?: boolean, version?: number): Promise<{
|
|
26
30
|
headers: undefined;
|
|
27
31
|
data: undefined;
|
|
28
32
|
metadata: string[];
|
|
@@ -36,7 +40,7 @@ export declare class Single implements Client {
|
|
|
36
40
|
profile<_T>(graph: string, query: string): Promise<string[]>;
|
|
37
41
|
list(): Promise<string[]>;
|
|
38
42
|
stubs(): Promise<string[]>;
|
|
39
|
-
configGet(configKey: string): Promise<(string | number)[] | (string | number)[]
|
|
43
|
+
configGet(configKey: string): Promise<(string | number)[][] | (string | number)[]>;
|
|
40
44
|
configSet(configKey: string, value: number | string): Promise<void>;
|
|
41
45
|
info(section?: string): Promise<(string | string[])[]>;
|
|
42
46
|
slowLog(graph: string): Promise<{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Single = void 0;
|
|
4
|
+
const READ_ONLY = { readOnly: true };
|
|
4
5
|
class Single {
|
|
5
6
|
client;
|
|
6
7
|
constructor(client) {
|
|
@@ -9,66 +10,73 @@ class Single {
|
|
|
9
10
|
init(_falkordb) {
|
|
10
11
|
return Promise.resolve();
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Runs a command against the underlying connection.
|
|
15
|
+
* Subclasses override it to add recovery, e.g. retrying on a new master.
|
|
16
|
+
*/
|
|
17
|
+
run(fn, _options = {}) {
|
|
18
|
+
return fn(this.client);
|
|
14
19
|
}
|
|
15
|
-
async
|
|
16
|
-
return await this.client.falkordb.
|
|
20
|
+
async query(graph, query, options, compact = true, version) {
|
|
21
|
+
return await this.run((client) => client.falkordb.query(graph, query, options, compact, version));
|
|
22
|
+
}
|
|
23
|
+
async roQuery(graph, query, options, compact = true, version) {
|
|
24
|
+
return await this.run((client) => client.falkordb.roQuery(graph, query, options, compact, version), READ_ONLY);
|
|
17
25
|
}
|
|
18
26
|
async delete(graph) {
|
|
19
|
-
const reply = this.client.falkordb.delete(graph);
|
|
27
|
+
const reply = this.run((client) => client.falkordb.delete(graph));
|
|
20
28
|
return reply.then(() => { });
|
|
21
29
|
}
|
|
22
30
|
async explain(graph, query) {
|
|
23
|
-
return this.client.falkordb.explain(graph, query);
|
|
31
|
+
return this.run((client) => client.falkordb.explain(graph, query), READ_ONLY);
|
|
24
32
|
}
|
|
25
33
|
async profile(graph, query) {
|
|
26
|
-
return this.client.falkordb.profile(graph, query);
|
|
34
|
+
return this.run((client) => client.falkordb.profile(graph, query));
|
|
27
35
|
}
|
|
28
36
|
async list() {
|
|
29
|
-
return this.client.falkordb.list();
|
|
37
|
+
return this.run((client) => client.falkordb.list(), READ_ONLY);
|
|
30
38
|
}
|
|
31
39
|
async stubs() {
|
|
32
|
-
return this.client.falkordb.stubs();
|
|
40
|
+
return this.run((client) => client.falkordb.stubs());
|
|
33
41
|
}
|
|
34
42
|
async configGet(configKey) {
|
|
35
|
-
return this.client.falkordb.configGet(configKey);
|
|
43
|
+
return this.run((client) => client.falkordb.configGet(configKey), READ_ONLY);
|
|
36
44
|
}
|
|
37
45
|
async configSet(configKey, value) {
|
|
38
|
-
const reply = this.client.falkordb.configSet(configKey, value);
|
|
46
|
+
const reply = this.run((client) => client.falkordb.configSet(configKey, value));
|
|
39
47
|
return reply.then(() => { });
|
|
40
48
|
}
|
|
41
49
|
async info(section) {
|
|
42
|
-
return this.client.falkordb.info(section);
|
|
50
|
+
return this.run((client) => client.falkordb.info(section), READ_ONLY);
|
|
43
51
|
}
|
|
44
52
|
async slowLog(graph) {
|
|
45
|
-
return this.client.falkordb.slowLog(graph);
|
|
53
|
+
return this.run((client) => client.falkordb.slowLog(graph), READ_ONLY);
|
|
46
54
|
}
|
|
47
55
|
async memoryUsage(graph, options) {
|
|
48
|
-
return this.client.falkordb.memoryUsage(graph, options);
|
|
56
|
+
return this.run((client) => client.falkordb.memoryUsage(graph, options), READ_ONLY);
|
|
49
57
|
}
|
|
50
58
|
async constraintCreate(graph, constraintType, entityType, label, ...properties) {
|
|
51
|
-
const reply = this.client.falkordb.constraintCreate(graph, constraintType, entityType, label, ...properties);
|
|
59
|
+
const reply = this.run((client) => client.falkordb.constraintCreate(graph, constraintType, entityType, label, ...properties));
|
|
52
60
|
return reply.then(() => { });
|
|
53
61
|
}
|
|
54
62
|
async constraintDrop(graph, constraintType, entityType, label, ...properties) {
|
|
55
|
-
const reply = this.client.falkordb.constraintDrop(graph, constraintType, entityType, label, ...properties);
|
|
63
|
+
const reply = this.run((client) => client.falkordb.constraintDrop(graph, constraintType, entityType, label, ...properties));
|
|
56
64
|
return reply.then(() => { });
|
|
57
65
|
}
|
|
58
66
|
async udfLoad(name, script, replace = false) {
|
|
59
|
-
return this.client.falkordb.udfLoad(name, script, replace);
|
|
67
|
+
return this.run((client) => client.falkordb.udfLoad(name, script, replace));
|
|
60
68
|
}
|
|
61
69
|
async udfList(lib, withCode = false) {
|
|
62
|
-
return this.client.falkordb.udfList(lib, withCode);
|
|
70
|
+
return this.run((client) => client.falkordb.udfList(lib, withCode), READ_ONLY);
|
|
63
71
|
}
|
|
64
72
|
async udfFlush() {
|
|
65
|
-
return this.client.falkordb.udfFlush();
|
|
73
|
+
return this.run((client) => client.falkordb.udfFlush());
|
|
66
74
|
}
|
|
67
75
|
async udfDelete(lib) {
|
|
68
|
-
return this.client.falkordb.udfDelete(lib);
|
|
76
|
+
return this.run((client) => client.falkordb.udfDelete(lib));
|
|
69
77
|
}
|
|
70
78
|
async copy(srcGraph, destGraph) {
|
|
71
|
-
return this.client.falkordb.copy(srcGraph, destGraph);
|
|
79
|
+
return this.run((client) => client.falkordb.copy(srcGraph, destGraph));
|
|
72
80
|
}
|
|
73
81
|
quit() {
|
|
74
82
|
return this.disconnect();
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { CommandParser } from '@redis/client';
|
|
2
|
+
import type { ConfigItem } from '../types';
|
|
2
3
|
export declare const IS_READ_ONLY = true;
|
|
3
4
|
export declare function parseCommand(parser: CommandParser, configKey: string): void;
|
|
4
5
|
export declare function transformArguments(configKey: string): Array<string>;
|
|
5
|
-
type ConfigItem = [
|
|
6
|
-
configKey: string,
|
|
7
|
-
value: number
|
|
8
|
-
];
|
|
9
6
|
export declare function transformReply(reply: unknown): ConfigItem | Array<ConfigItem>;
|
|
10
|
-
export {};
|