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.
Files changed (72) hide show
  1. package/dist/index.d.ts +3 -2
  2. package/dist/index.js +8 -5
  3. package/dist/src/clients/client.d.ts +5 -13
  4. package/dist/src/clients/cluster.d.ts +7 -12
  5. package/dist/src/clients/cluster.js +38 -5
  6. package/dist/src/clients/nullClient.d.ts +3 -11
  7. package/dist/src/clients/sentinel.d.ts +31 -0
  8. package/dist/src/clients/sentinel.js +179 -24
  9. package/dist/src/clients/single.d.ts +16 -12
  10. package/dist/src/clients/single.js +29 -21
  11. package/dist/src/commands/CONFIG_GET.d.ts +1 -5
  12. package/dist/src/commands/CONSTRAINT_CREATE.d.ts +2 -8
  13. package/dist/src/commands/CONSTRAINT_CREATE.js +3 -10
  14. package/dist/src/commands/CONSTRAINT_DROP.d.ts +1 -1
  15. package/dist/src/commands/MEMORY_USAGE.d.ts +2 -4
  16. package/dist/src/commands/QUERY.d.ts +4 -23
  17. package/dist/src/commands/QUERY.js +14 -4
  18. package/dist/src/commands/RO_QUERY.d.ts +2 -2
  19. package/dist/src/commands/RO_QUERY.js +4 -4
  20. package/dist/src/commands/SLOWLOG.d.ts +1 -13
  21. package/dist/src/commands/UDF_LIST.d.ts +2 -2
  22. package/dist/src/commands/index.d.ts +4 -11
  23. package/dist/src/commands/index.js +58 -22
  24. package/dist/src/enums/constraint-type.d.ts +4 -0
  25. package/dist/src/enums/constraint-type.js +8 -0
  26. package/dist/src/enums/entity-type.d.ts +4 -0
  27. package/dist/src/enums/entity-type.js +8 -0
  28. package/dist/src/enums/graph-value-types.d.ts +19 -0
  29. package/dist/src/enums/graph-value-types.js +24 -0
  30. package/dist/src/enums/index.d.ts +3 -0
  31. package/dist/src/enums/index.js +19 -0
  32. package/dist/src/errors.d.ts +10 -0
  33. package/dist/src/errors.js +18 -0
  34. package/dist/src/falkordb.d.ts +13 -88
  35. package/dist/src/falkordb.js +131 -8
  36. package/dist/src/graph.d.ts +27 -15
  37. package/dist/src/graph.js +90 -52
  38. package/dist/src/types/cluster-graph-connection.d.ts +5 -0
  39. package/dist/src/types/cluster-graph-connection.js +2 -0
  40. package/dist/src/types/config-item.d.ts +4 -0
  41. package/dist/src/types/config-item.js +2 -0
  42. package/dist/src/types/falkordb-options.d.ts +65 -0
  43. package/dist/src/types/falkordb-options.js +2 -0
  44. package/dist/src/types/graph-metadata.d.ts +5 -0
  45. package/dist/src/types/graph-metadata.js +2 -0
  46. package/dist/src/types/graph-raw-value.d.ts +29 -0
  47. package/dist/src/types/graph-raw-value.js +2 -0
  48. package/dist/src/types/graph-value.d.ts +29 -0
  49. package/dist/src/types/graph-value.js +2 -0
  50. package/dist/src/types/index.d.ts +16 -0
  51. package/dist/src/types/index.js +32 -0
  52. package/dist/src/types/memory-usage.d.ts +4 -0
  53. package/dist/src/types/memory-usage.js +2 -0
  54. package/dist/src/types/net-socket-options.d.ts +4 -0
  55. package/dist/src/types/net-socket-options.js +2 -0
  56. package/dist/src/types/query-options.d.ts +9 -0
  57. package/dist/src/types/query-options.js +2 -0
  58. package/dist/src/types/query-reply.d.ts +20 -0
  59. package/dist/src/types/query-reply.js +2 -0
  60. package/dist/src/types/single-graph-connection.d.ts +5 -0
  61. package/dist/src/types/single-graph-connection.js +2 -0
  62. package/dist/src/types/slow-log.d.ts +12 -0
  63. package/dist/src/types/slow-log.js +2 -0
  64. package/dist/src/types/socket-options.d.ts +40 -0
  65. package/dist/src/types/socket-options.js +2 -0
  66. package/dist/src/types/tls-socket-options.d.ts +4 -0
  67. package/dist/src/types/tls-socket-options.js +2 -0
  68. package/dist/src/types/typed-redis-client-options.d.ts +9 -0
  69. package/dist/src/types/typed-redis-client-options.js +2 -0
  70. package/dist/src/types/udf-list.d.ts +2 -0
  71. package/dist/src/types/udf-list.js +2 -0
  72. package/package.json +7 -6
@@ -1,27 +1,70 @@
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
  const events_1 = require("events");
4
7
  const redis_1 = require("redis");
5
- const graph_1 = require("./graph");
6
- const commands_1 = require("./commands");
8
+ const graph_1 = __importDefault(require("./graph"));
9
+ const commands_1 = __importDefault(require("./commands"));
10
+ const client_1 = require("@redis/client");
7
11
  const single_1 = require("./clients/single");
8
12
  const sentinel_1 = require("./clients/sentinel");
9
13
  const cluster_1 = require("./clients/cluster");
10
14
  const nullClient_1 = require("./clients/nullClient");
11
- async function clientFactory(client) {
15
+ /**
16
+ * Retries the initial connection gets before `FalkorDB.connect()` rejects.
17
+ */
18
+ const INITIAL_CONNECT_RETRIES = 3;
19
+ /**
20
+ * node-redis' default back-off: exponential up to 2s plus jitter, and no
21
+ * reconnect after a socket timeout.
22
+ */
23
+ function defaultReconnectStrategy(retries, cause) {
24
+ if (cause instanceof client_1.SocketTimeoutError) {
25
+ return false;
26
+ }
27
+ return Math.min(Math.pow(2, retries) * 50, 2000) + Math.floor(Math.random() * 200);
28
+ }
29
+ function initialReconnectStrategy(retries, cause, tlsEnabled) {
30
+ if (retries < INITIAL_CONNECT_RETRIES) {
31
+ return defaultReconnectStrategy(retries, cause);
32
+ }
33
+ // A TLS server drops a plain-text client mid-handshake, which node-redis
34
+ // reports as the socket closing unexpectedly.
35
+ const hint = !tlsEnabled && cause instanceof client_1.SocketClosedUnexpectedlyError
36
+ ? ' The server closed the connection during the handshake; if it requires TLS, set `socket.tls: true` or use a `rediss://` URL.'
37
+ : '';
38
+ return new Error(`Failed to connect to FalkorDB: ${cause.message}.${hint}`, { cause });
39
+ }
40
+ async function clientFactory(client, sentinelOptions) {
12
41
  const info = await client.info("server");
13
42
  if (info.includes("redis_mode:sentinel")) {
14
- return new sentinel_1.Sentinel(client);
43
+ return new sentinel_1.Sentinel(client, sentinelOptions);
15
44
  }
16
45
  else if (info.includes("redis_mode:cluster")) {
17
46
  return new cluster_1.Cluster(client);
18
47
  }
19
48
  return new single_1.Single(client);
20
49
  }
50
+ // Upper bound on the `Graph` instances `selectGraph` keeps. Past it the least
51
+ // recently selected one is dropped; that only costs its metadata cache, which
52
+ // the next `selectGraph` for that id rebuilds.
53
+ const MAX_CACHED_GRAPHS = 1000;
21
54
  class FalkorDB extends events_1.EventEmitter {
22
55
  #client = new nullClient_1.NullClient();
56
+ #graphs = new Map();
57
+ // Shared by every instance for a graph id, including ones evicted from
58
+ // #graphs that callers still hold. Weakly held, so an entry lives only as
59
+ // long as some instance using it does.
60
+ #schemas = new Map();
61
+ #schemasCleanup = new FinalizationRegistry((graphId) => {
62
+ if (!this.#schemas.get(graphId)?.deref()) {
63
+ this.#schemas.delete(graphId);
64
+ }
65
+ });
23
66
  static async connect(options) {
24
- const { host, port, ...rest } = options ?? {};
67
+ const { host, port, retryWritesOnConnectionLoss, ...rest } = options ?? {};
25
68
  const redisOption = rest;
26
69
  // Support the top-level `host`/`port` shorthand used by the other FalkorDB
27
70
  // clients by folding them into the socket options node-redis expects.
@@ -41,22 +84,100 @@ class FalkorDB extends events_1.EventEmitter {
41
84
  redisOption.modules = {
42
85
  falkordb: commands_1.default
43
86
  };
87
+ // node-redis v6 defaults to RESP3, which auto-converts module map replies
88
+ // into nested objects: `GRAPH.MEMORY USAGE` comes back as
89
+ // `{ total_graph_sz_mb: 0, ... }` instead of the flat
90
+ // `['total_graph_sz_mb', 0, ...]` that the reply parsers - and the
91
+ // `MemoryUsageReply` array type - are written against. So RESP 2 is a
92
+ // requirement here, not a default: honouring an explicit RESP 3 would
93
+ // hand the caller a reply shape the published types say is impossible.
94
+ // The sentinel and cluster clients reuse this options object, so they
95
+ // inherit the pin too.
96
+ const requestedRESP = redisOption.RESP;
97
+ if (requestedRESP !== undefined && requestedRESP !== 2) {
98
+ throw new Error(`FalkorDB requires RESP 2, but RESP ${requestedRESP} was requested. ` +
99
+ 'Under RESP 3 node-redis rewrites module map replies into objects, which does ' +
100
+ 'not match the reply shapes this client parses and publishes types for.');
101
+ }
102
+ redisOption.RESP = 2;
103
+ // node-redis retries the first connection forever by default, so a server
104
+ // that keeps closing the socket - e.g. a TLS server reached without TLS -
105
+ // leaves `connect()` pending indefinitely. Unless the caller chose a
106
+ // strategy, give up after a few attempts while connecting and keep the
107
+ // usual unbounded back-off once connected. The sentinel and cluster
108
+ // clients reuse these socket options, so `connected` is only set once
109
+ // they are up too: a node discovered during `init()` that cannot be
110
+ // reached must fail `connect()` as well.
111
+ let connected = false;
112
+ if (redisOption.socket?.reconnectStrategy === undefined) {
113
+ const tlsEnabled = redisOption.socket?.tls === true || !!redisOption.url?.startsWith('rediss:');
114
+ redisOption.socket = {
115
+ ...redisOption.socket,
116
+ reconnectStrategy: (retries, cause) => connected
117
+ ? defaultReconnectStrategy(retries, cause)
118
+ : initialReconnectStrategy(retries, cause, tlsEnabled),
119
+ };
120
+ }
44
121
  // Create an empty FalkorDB instance for the redisClient on error event to work
45
122
  const falkordb = new FalkorDB();
46
123
  // Create initial redis single client
47
124
  const redisClient = (0, redis_1.createClient)(redisOption);
48
125
  await redisClient
49
- .on('error', err => falkordb.emit('error', err)) // Forward errors
126
+ // Forward errors. Nobody can listen on `falkordb` before it is returned,
127
+ // so emitting while connecting would only throw an unhandled 'error';
128
+ // those failures reach the caller as the rejection of `connect()`.
129
+ .on('error', err => { if (connected)
130
+ falkordb.emit('error', err); })
50
131
  .connect();
51
132
  // Create FalkorDB client wrapper
52
- const client = await clientFactory(redisClient);
133
+ const client = await clientFactory(redisClient, { retryWritesOnConnectionLoss });
53
134
  await client.init(falkordb);
135
+ connected = true;
54
136
  // Set the client to the FalkorDB instance after it was initialized
55
137
  falkordb.#client = client;
56
138
  return falkordb;
57
139
  }
140
+ /**
141
+ * Returns the `Graph` for `graphId`. Repeated calls with the same id return
142
+ * the same instance, so its cached metadata (labels, relationship types and
143
+ * property keys) is shared instead of being refetched by every new handle.
144
+ * The instance is released when the graph is deleted through it, or when
145
+ * more than `MAX_CACHED_GRAPHS` graphs have been selected since its last use.
146
+ * Each query carries the schema version the cached metadata matches, so a
147
+ * schema changed by another client (e.g. the graph deleted and recreated) is
148
+ * rejected by the server and the metadata refreshed before the query is retried.
149
+ */
58
150
  selectGraph(graphId) {
59
- return new graph_1.default(this.#client, graphId);
151
+ let graph = this.#graphs.get(graphId);
152
+ if (graph) {
153
+ // Re-insert so the Map's insertion order tracks recency of use
154
+ this.#graphs.delete(graphId);
155
+ }
156
+ else {
157
+ graph = new graph_1.default(this.#client, graphId, {
158
+ schema: this.#schema(graphId),
159
+ onDelete: (deleted) => {
160
+ // An instance already evicted must not remove its replacement
161
+ if (this.#graphs.get(graphId) === deleted) {
162
+ this.#graphs.delete(graphId);
163
+ }
164
+ },
165
+ });
166
+ }
167
+ this.#graphs.set(graphId, graph);
168
+ if (this.#graphs.size > MAX_CACHED_GRAPHS) {
169
+ this.#graphs.delete(this.#graphs.keys().next().value);
170
+ }
171
+ return graph;
172
+ }
173
+ #schema(graphId) {
174
+ let schema = this.#schemas.get(graphId)?.deref();
175
+ if (!schema) {
176
+ schema = { generation: 0, version: 0 };
177
+ this.#schemas.set(graphId, new WeakRef(schema));
178
+ this.#schemasCleanup.register(schema, graphId);
179
+ }
180
+ return schema;
60
181
  }
61
182
  get connection() {
62
183
  return this.#client.getConnection();
@@ -100,6 +221,8 @@ class FalkorDB extends events_1.EventEmitter {
100
221
  * Closes the client.
101
222
  */
102
223
  async close() {
224
+ this.#graphs.clear();
225
+ this.#schemas.clear();
103
226
  return this.#client.disconnect();
104
227
  }
105
228
  }
@@ -1,28 +1,40 @@
1
1
  import { RedisArgument } from "@redis/client";
2
- import { QueryOptions } from "./commands";
3
- import { QueryReply } from "./commands/QUERY";
4
- import { ConstraintType, EntityType } from "./commands/CONSTRAINT_CREATE";
5
2
  import { Client } from "./clients/client";
6
- import { MemoryUsageOptions } from "./commands/MEMORY_USAGE";
3
+ import { ConstraintType, EntityType } from "./enums";
4
+ import type { GraphReply, MemoryUsageOptions, QueryOptions, QueryReply } from "./types";
7
5
  export { ConstraintType, EntityType };
8
- export type GraphReply<T> = Omit<QueryReply, "headers" | "data"> & {
9
- data?: Array<T>;
10
- };
6
+ export type { GraphReply };
7
+ /**
8
+ * What the client knows about a graph's schema. Every `Graph` instance for the
9
+ * same graph shares one, so a change seen through any of them invalidates the
10
+ * metadata cached by all of them.
11
+ */
12
+ export interface GraphSchemaState {
13
+ generation: number;
14
+ version: number;
15
+ }
16
+ export interface GraphCacheOptions {
17
+ schema?: GraphSchemaState;
18
+ onDelete?: (graph: Graph) => void;
19
+ }
11
20
  export default class Graph {
12
21
  #private;
13
- constructor(client: Client, name: string);
22
+ constructor(client: Client, name: string, cache?: GraphCacheOptions);
14
23
  query<T>(query: RedisArgument, options?: QueryOptions): Promise<GraphReply<T>>;
15
24
  roQuery<T>(query: RedisArgument, options?: QueryOptions): Promise<GraphReply<T>>;
16
25
  delete(): Promise<void>;
26
+ /**
27
+ * Drops the cached labels, relationship types and property keys, here and in
28
+ * every other instance `selectGraph` returned for this graph, so they are
29
+ * refetched the next time a reply needs them. A fetch already in flight does
30
+ * not repopulate the cache. Schema changes made by other clients are detected
31
+ * on the next query, so this is rarely needed.
32
+ */
33
+ clearMetadataCache(): void;
17
34
  explain(query: string): Promise<any>;
18
35
  profile(query: string): Promise<any>;
19
- memoryUsage(options?: MemoryUsageOptions): Promise<import("./commands/MEMORY_USAGE").MemoryUsageReply>;
20
- slowLog(): Promise<{
21
- timestamp: Date;
22
- command: string;
23
- query: string;
24
- took: number;
25
- }[]>;
36
+ memoryUsage(options?: MemoryUsageOptions): Promise<import("./types").MemoryUsageReply>;
37
+ slowLog(): Promise<import("./types").SlowLogReply>;
26
38
  constraintCreate(constraintType: ConstraintType, entityType: EntityType, label: string, ...properties: string[]): Promise<void>;
27
39
  constraintDrop(constraintType: ConstraintType, entityType: EntityType, label: string, ...properties: string[]): Promise<void>;
28
40
  copy(destGraph: string): Promise<any>;
package/dist/src/graph.js CHANGED
@@ -1,50 +1,68 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EntityType = exports.ConstraintType = void 0;
4
- const CONSTRAINT_CREATE_1 = require("./commands/CONSTRAINT_CREATE");
5
- Object.defineProperty(exports, "ConstraintType", { enumerable: true, get: function () { return CONSTRAINT_CREATE_1.ConstraintType; } });
6
- Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return CONSTRAINT_CREATE_1.EntityType; } });
7
4
  const polyfill_1 = require("@js-temporal/polyfill");
8
- // https://github.com/FalkorDB/FalkorDB/blob/master/src/resultset/formatters/resultset_formatter.h#L20
9
- var GraphValueTypes;
10
- (function (GraphValueTypes) {
11
- GraphValueTypes[GraphValueTypes["UNKNOWN"] = 0] = "UNKNOWN";
12
- GraphValueTypes[GraphValueTypes["NULL"] = 1] = "NULL";
13
- GraphValueTypes[GraphValueTypes["STRING"] = 2] = "STRING";
14
- GraphValueTypes[GraphValueTypes["INTEGER"] = 3] = "INTEGER";
15
- GraphValueTypes[GraphValueTypes["BOOLEAN"] = 4] = "BOOLEAN";
16
- GraphValueTypes[GraphValueTypes["DOUBLE"] = 5] = "DOUBLE";
17
- GraphValueTypes[GraphValueTypes["ARRAY"] = 6] = "ARRAY";
18
- GraphValueTypes[GraphValueTypes["EDGE"] = 7] = "EDGE";
19
- GraphValueTypes[GraphValueTypes["NODE"] = 8] = "NODE";
20
- GraphValueTypes[GraphValueTypes["PATH"] = 9] = "PATH";
21
- GraphValueTypes[GraphValueTypes["MAP"] = 10] = "MAP";
22
- GraphValueTypes[GraphValueTypes["POINT"] = 11] = "POINT";
23
- GraphValueTypes[GraphValueTypes["VECTORF32"] = 12] = "VECTORF32";
24
- GraphValueTypes[GraphValueTypes["DATETIME"] = 13] = "DATETIME";
25
- GraphValueTypes[GraphValueTypes["DATE"] = 14] = "DATE";
26
- GraphValueTypes[GraphValueTypes["TIME"] = 15] = "TIME";
27
- GraphValueTypes[GraphValueTypes["DURATION"] = 16] = "DURATION";
28
- })(GraphValueTypes || (GraphValueTypes = {}));
29
- // export type GraphConnection = SingleGraphConnection | ClusterGraphConnection;
5
+ const enums_1 = require("./enums");
6
+ Object.defineProperty(exports, "ConstraintType", { enumerable: true, get: function () { return enums_1.ConstraintType; } });
7
+ Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return enums_1.EntityType; } });
8
+ const errors_1 = require("./errors");
30
9
  class Graph {
31
10
  #client;
32
11
  #name;
33
12
  #metadata;
34
- constructor(client, name) {
13
+ #schema;
14
+ #onDelete;
15
+ constructor(client, name, cache) {
35
16
  this.#client = client;
36
17
  this.#name = name;
18
+ this.#schema = cache?.schema ?? { generation: 0, version: 0 };
19
+ this.#onDelete = cache?.onDelete;
37
20
  }
38
21
  async query(query, options) {
39
- const reply = await this.#client.query(this.#name, query, options);
22
+ const reply = await this.#withSchemaVersion((version) => this.#client.query(this.#name, query, options, true, version));
40
23
  return this.#parseReply(reply);
41
24
  }
42
25
  async roQuery(query, options) {
43
- const reply = await this.#client.roQuery(this.#name, query, options);
26
+ const reply = await this.#withSchemaVersion((version) => this.#client.roQuery(this.#name, query, options, true, version));
44
27
  return this.#parseReply(reply);
45
28
  }
29
+ async #withSchemaVersion(run) {
30
+ try {
31
+ return await run(this.#schema.version);
32
+ }
33
+ catch (err) {
34
+ if (!(err instanceof errors_1.SchemaVersionMismatchError))
35
+ throw err;
36
+ // The server rejected the query without running it, so retrying is
37
+ // safe. Metadata fetched from here on is at least as new as
38
+ // `err.version`, so pairing the two is sound. The retry is sent without
39
+ // a version so that further concurrent schema changes cannot make it
40
+ // loop; names they add get new ids and are fetched when needed.
41
+ this.clearMetadataCache();
42
+ this.#schema.version = err.version;
43
+ return run();
44
+ }
45
+ }
46
46
  async delete() {
47
- return this.#client.delete(this.#name);
47
+ const reply = await this.#client.delete(this.#name);
48
+ // The instance is reused by `selectGraph`, and a graph recreated under the
49
+ // same name assigns new ids, so the cached metadata must not outlive it.
50
+ this.clearMetadataCache();
51
+ // A recreated graph starts from an empty schema, whose version is 0
52
+ this.#schema.version = 0;
53
+ this.#onDelete?.(this);
54
+ return reply;
55
+ }
56
+ /**
57
+ * Drops the cached labels, relationship types and property keys, here and in
58
+ * every other instance `selectGraph` returned for this graph, so they are
59
+ * refetched the next time a reply needs them. A fetch already in flight does
60
+ * not repopulate the cache. Schema changes made by other clients are detected
61
+ * on the next query, so this is rarely needed.
62
+ */
63
+ clearMetadataCache() {
64
+ this.#schema.generation++;
65
+ this.#metadata = undefined;
48
66
  }
49
67
  async explain(query) {
50
68
  return this.#client.explain(this.#name, query);
@@ -67,30 +85,50 @@ class Graph {
67
85
  async copy(destGraph) {
68
86
  return this.#client.copy(this.#name, destGraph);
69
87
  }
70
- #setMetadataPromise;
88
+ #pendingMetadata;
71
89
  #updateMetadata() {
72
- this.#setMetadataPromise ??= this.#setMetadata().finally(() => (this.#setMetadataPromise = undefined));
73
- return this.#setMetadataPromise;
90
+ const generation = this.#schema.generation;
91
+ // A fetch started before the metadata was invalidated is not joined
92
+ if (this.#pendingMetadata?.generation !== generation) {
93
+ const pending = {
94
+ generation,
95
+ promise: this.#setMetadata(generation).finally(() => {
96
+ if (this.#pendingMetadata === pending) {
97
+ this.#pendingMetadata = undefined;
98
+ }
99
+ }),
100
+ };
101
+ this.#pendingMetadata = pending;
102
+ }
103
+ return this.#pendingMetadata.promise;
74
104
  }
75
105
  // DO NOT use directly, use #updateMetadata instead
76
- async #setMetadata() {
106
+ async #setMetadata(generation) {
77
107
  const [labels, relationshipTypes, propertyKeys] = await Promise.all([
78
108
  this.#client.roQuery(this.#name, "CALL db.labels()", undefined, false),
79
109
  this.#client.roQuery(this.#name, "CALL db.relationshipTypes()", undefined, false),
80
110
  this.#client.roQuery(this.#name, "CALL db.propertyKeys()", undefined, false),
81
111
  ]);
82
- this.#metadata = {
112
+ const metadata = {
83
113
  labels: this.#cleanMetadataArray(labels.data),
84
114
  relationshipTypes: this.#cleanMetadataArray(relationshipTypes.data),
85
115
  propertyKeys: this.#cleanMetadataArray(propertyKeys.data),
86
116
  };
87
- return this.#metadata;
117
+ // The caller's reply predates any reset, so it still gets these names,
118
+ // but they are only cached if the graph was not deleted meanwhile
119
+ if (generation === this.#schema.generation) {
120
+ this.#metadata = { generation, names: metadata };
121
+ }
122
+ return metadata;
88
123
  }
89
124
  #cleanMetadataArray(arr) {
90
125
  return arr.map(([value]) => value);
91
126
  }
92
127
  #getMetadata(key, id) {
93
- return this.#metadata?.[key][id] ?? this.#getMetadataAsync(key, id);
128
+ const metadata = this.#metadata?.generation === this.#schema.generation
129
+ ? this.#metadata.names
130
+ : undefined;
131
+ return metadata?.[key][id] ?? this.#getMetadataAsync(key, id);
94
132
  }
95
133
  // DO NOT use directly, use #getMetadata instead
96
134
  async #getMetadataAsync(key, id) {
@@ -121,53 +159,53 @@ class Graph {
121
159
  }
122
160
  #parseValue([valueType, value], promises) {
123
161
  switch (valueType) {
124
- case GraphValueTypes.NULL:
162
+ case enums_1.GraphValueTypes.NULL:
125
163
  return null;
126
- case GraphValueTypes.STRING:
127
- case GraphValueTypes.INTEGER:
164
+ case enums_1.GraphValueTypes.STRING:
165
+ case enums_1.GraphValueTypes.INTEGER:
128
166
  return value;
129
- case GraphValueTypes.BOOLEAN:
167
+ case enums_1.GraphValueTypes.BOOLEAN:
130
168
  return value === "true";
131
- case GraphValueTypes.DOUBLE:
169
+ case enums_1.GraphValueTypes.DOUBLE:
132
170
  return parseFloat(value);
133
- case GraphValueTypes.ARRAY:
171
+ case enums_1.GraphValueTypes.ARRAY:
134
172
  return value.map((x) => this.#parseValue(x, promises));
135
- case GraphValueTypes.EDGE:
173
+ case enums_1.GraphValueTypes.EDGE:
136
174
  return this.#parseEdge(value, promises);
137
- case GraphValueTypes.NODE:
175
+ case enums_1.GraphValueTypes.NODE:
138
176
  return this.#parseNode(value, promises);
139
- case GraphValueTypes.PATH:
177
+ case enums_1.GraphValueTypes.PATH:
140
178
  return {
141
179
  nodes: value[0][1].map(([, node]) => this.#parseNode(node, promises)),
142
180
  edges: value[1][1].map(([, edge]) => this.#parseEdge(edge, promises)),
143
181
  };
144
- case GraphValueTypes.MAP: {
182
+ case enums_1.GraphValueTypes.MAP: {
145
183
  const map = {};
146
184
  for (let i = 0; i < value.length; i++) {
147
185
  map[value[i++]] = this.#parseValue(value[i], promises);
148
186
  }
149
187
  return map;
150
188
  }
151
- case GraphValueTypes.POINT:
189
+ case enums_1.GraphValueTypes.POINT:
152
190
  return {
153
191
  latitude: parseFloat(value[0]),
154
192
  longitude: parseFloat(value[1]),
155
193
  };
156
- case GraphValueTypes.VECTORF32:
194
+ case enums_1.GraphValueTypes.VECTORF32:
157
195
  return value.map((x) => Number(x));
158
- case GraphValueTypes.DATETIME:
196
+ case enums_1.GraphValueTypes.DATETIME:
159
197
  return polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000)
160
198
  .toZonedDateTimeISO("UTC")
161
199
  .toPlainDateTime();
162
- case GraphValueTypes.DATE:
200
+ case enums_1.GraphValueTypes.DATE:
163
201
  return polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000)
164
202
  .toZonedDateTimeISO("UTC")
165
203
  .toPlainDate();
166
- case GraphValueTypes.TIME:
204
+ case enums_1.GraphValueTypes.TIME:
167
205
  return polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000)
168
206
  .toZonedDateTimeISO("UTC")
169
207
  .toPlainTime();
170
- case GraphValueTypes.DURATION:
208
+ case enums_1.GraphValueTypes.DURATION:
171
209
  const time = polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000);
172
210
  const epoch = polyfill_1.Temporal.Instant.fromEpochMilliseconds(0);
173
211
  return epoch
@@ -0,0 +1,5 @@
1
+ import type { RedisClusterType, RedisFunctions, RedisScripts } from '@redis/client';
2
+ import type commands from '../commands';
3
+ export type ClusterGraphConnection = RedisClusterType<{
4
+ falkordb: typeof commands;
5
+ }, RedisFunctions, RedisScripts, 2>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export type ConfigItem = [
2
+ configKey: string,
3
+ value: number
4
+ ];
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,65 @@
1
+ import type { SocketOptions } from './socket-options';
2
+ export interface FalkorDBOptions {
3
+ /**
4
+ * `falkor[s]://[[username][:password]@][host][:port][/db-number]`
5
+ */
6
+ url?: string;
7
+ /**
8
+ * Socket connection properties
9
+ */
10
+ socket?: SocketOptions;
11
+ /**
12
+ * Server hostname, shorthand for `socket.host`.
13
+ * Ignored when `url` or `socket.host` is provided.
14
+ */
15
+ host?: string;
16
+ /**
17
+ * Server port, shorthand for `socket.port`.
18
+ * Ignored when `url` or `socket.port` is provided.
19
+ */
20
+ port?: number;
21
+ /**
22
+ * Sentinel only. Resend write commands to the new master when the
23
+ * connection is lost while waiting for their reply, e.g. during a master
24
+ * hand-over. The old master may have already run them, so a write can be
25
+ * applied twice. Read-only commands, and commands the server certainly did
26
+ * not run (such as ones rejected with `READONLY`), are always resent.
27
+ * @default false
28
+ */
29
+ retryWritesOnConnectionLoss?: boolean;
30
+ /**
31
+ * ACL username ([see ACL guide](https://redis.io/topics/acl))
32
+ */
33
+ username?: string;
34
+ /**
35
+ * ACL password or the old "--requirepass" password
36
+ */
37
+ password?: string;
38
+ /**
39
+ * Client name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname))
40
+ */
41
+ name?: string;
42
+ /**
43
+ * Connect in [`READONLY`](https://redis.io/commands/readonly) mode
44
+ */
45
+ readonly?: boolean;
46
+ /**
47
+ * Send `PING` command at interval (in ms).
48
+ * Useful with Redis deployments that do not use TCP Keep-Alive.
49
+ */
50
+ pingInterval?: number;
51
+ /**
52
+ * If set to true, disables sending client identifier (user-agent like message) to the redis server
53
+ */
54
+ disableClientInfo?: boolean;
55
+ /**
56
+ * Tag to append to library name that is sent to the Redis server
57
+ */
58
+ clientInfoTag?: string;
59
+ /**
60
+ * RESP protocol version. Only RESP 2 is supported: under RESP 3 node-redis rewrites
61
+ * module map replies into objects, which does not match the reply shapes this client
62
+ * parses and publishes types for. Passing anything else throws.
63
+ */
64
+ RESP?: 2;
65
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export interface GraphMetadata {
2
+ labels: Array<string>;
3
+ relationshipTypes: Array<string>;
4
+ propertyKeys: Array<string>;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,29 @@
1
+ import type { GraphValueTypes } from '../enums';
2
+ export type GraphEntityRawProperties = Array<[id: number, ...value: GraphRawValue]>;
3
+ export type GraphEdgeRawValue = [
4
+ GraphValueTypes.EDGE,
5
+ [
6
+ id: number,
7
+ relationshipTypeId: number,
8
+ sourceId: number,
9
+ destinationId: number,
10
+ properties: GraphEntityRawProperties
11
+ ]
12
+ ];
13
+ export type GraphNodeRawValue = [
14
+ GraphValueTypes.NODE,
15
+ [
16
+ id: number,
17
+ labelIds: Array<number>,
18
+ properties: GraphEntityRawProperties
19
+ ]
20
+ ];
21
+ export type GraphPathRawValue = [
22
+ GraphValueTypes.PATH,
23
+ [
24
+ nodes: [GraphValueTypes.ARRAY, Array<GraphNodeRawValue>],
25
+ edges: [GraphValueTypes.ARRAY, Array<GraphEdgeRawValue>]
26
+ ]
27
+ ];
28
+ export type GraphMapRawValue = [GraphValueTypes.MAP, Array<string | GraphRawValue>];
29
+ export type GraphRawValue = [GraphValueTypes.NULL, null] | [GraphValueTypes.STRING, string] | [GraphValueTypes.INTEGER, number] | [GraphValueTypes.BOOLEAN, string] | [GraphValueTypes.DOUBLE, string] | [GraphValueTypes.ARRAY, Array<GraphRawValue>] | GraphEdgeRawValue | GraphNodeRawValue | GraphPathRawValue | GraphMapRawValue | [GraphValueTypes.POINT, [latitude: string, longitude: string]] | [GraphValueTypes.VECTORF32, number[]] | [GraphValueTypes.DATETIME, number] | [GraphValueTypes.DATE, number] | [GraphValueTypes.TIME, number] | [GraphValueTypes.DURATION, number];
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,29 @@
1
+ import type { Temporal } from '@js-temporal/polyfill';
2
+ import type { QueryReply } from './query-reply';
3
+ export type GraphEntityProperties = Record<string, GraphValue>;
4
+ export interface GraphEdge {
5
+ id: number;
6
+ relationshipType: string;
7
+ sourceId: number;
8
+ destinationId: number;
9
+ properties: GraphEntityProperties;
10
+ }
11
+ export interface GraphNode {
12
+ id: number;
13
+ labels: Array<string>;
14
+ properties: GraphEntityProperties;
15
+ }
16
+ export interface GraphPath {
17
+ nodes: Array<GraphNode>;
18
+ edges: Array<GraphEdge>;
19
+ }
20
+ export type GraphMap = {
21
+ [key: string]: GraphValue;
22
+ };
23
+ export type GraphValue = null | string | number | boolean | Array<GraphValue> | GraphEdge | GraphNode | GraphPath | GraphMap | {
24
+ latitude: string;
25
+ longitude: string;
26
+ } | number[] | Temporal.PlainDateTime | Temporal.PlainDate | Temporal.PlainTime | Temporal.Duration;
27
+ export type GraphReply<T> = Omit<QueryReply, "headers" | "data"> & {
28
+ data?: Array<T>;
29
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,16 @@
1
+ export * from './cluster-graph-connection';
2
+ export * from './config-item';
3
+ export * from './falkordb-options';
4
+ export * from './graph-metadata';
5
+ export * from './graph-raw-value';
6
+ export * from './graph-value';
7
+ export * from './memory-usage';
8
+ export * from './net-socket-options';
9
+ export * from './query-options';
10
+ export * from './query-reply';
11
+ export * from './single-graph-connection';
12
+ export * from './slow-log';
13
+ export * from './socket-options';
14
+ export * from './tls-socket-options';
15
+ export * from './typed-redis-client-options';
16
+ export * from './udf-list';