falkordb
Version:
A FalkorDB javascript library
298 lines (297 loc) • 12.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntityType = exports.ConstraintType = void 0;
const CONSTRAINT_CREATE_1 = require("./commands/CONSTRAINT_CREATE");
Object.defineProperty(exports, "ConstraintType", { enumerable: true, get: function () { return CONSTRAINT_CREATE_1.ConstraintType; } });
Object.defineProperty(exports, "EntityType", { enumerable: true, get: function () { return CONSTRAINT_CREATE_1.EntityType; } });
const polyfill_1 = require("@js-temporal/polyfill");
// https://github.com/FalkorDB/FalkorDB/blob/master/src/resultset/formatters/resultset_formatter.h#L20
var GraphValueTypes;
(function (GraphValueTypes) {
GraphValueTypes[GraphValueTypes["UNKNOWN"] = 0] = "UNKNOWN";
GraphValueTypes[GraphValueTypes["NULL"] = 1] = "NULL";
GraphValueTypes[GraphValueTypes["STRING"] = 2] = "STRING";
GraphValueTypes[GraphValueTypes["INTEGER"] = 3] = "INTEGER";
GraphValueTypes[GraphValueTypes["BOOLEAN"] = 4] = "BOOLEAN";
GraphValueTypes[GraphValueTypes["DOUBLE"] = 5] = "DOUBLE";
GraphValueTypes[GraphValueTypes["ARRAY"] = 6] = "ARRAY";
GraphValueTypes[GraphValueTypes["EDGE"] = 7] = "EDGE";
GraphValueTypes[GraphValueTypes["NODE"] = 8] = "NODE";
GraphValueTypes[GraphValueTypes["PATH"] = 9] = "PATH";
GraphValueTypes[GraphValueTypes["MAP"] = 10] = "MAP";
GraphValueTypes[GraphValueTypes["POINT"] = 11] = "POINT";
GraphValueTypes[GraphValueTypes["VECTORF32"] = 12] = "VECTORF32";
GraphValueTypes[GraphValueTypes["DATETIME"] = 13] = "DATETIME";
GraphValueTypes[GraphValueTypes["DATE"] = 14] = "DATE";
GraphValueTypes[GraphValueTypes["TIME"] = 15] = "TIME";
GraphValueTypes[GraphValueTypes["DURATION"] = 16] = "DURATION";
})(GraphValueTypes || (GraphValueTypes = {}));
// export type GraphConnection = SingleGraphConnection | ClusterGraphConnection;
class Graph {
#client;
#name;
#metadata;
constructor(client, name) {
this.#client = client;
this.#name = name;
}
async query(query, options) {
const reply = await this.#client.query(this.#name, query, options);
return this.#parseReply(reply);
}
async roQuery(query, options) {
const reply = await this.#client.roQuery(this.#name, query, options);
return this.#parseReply(reply);
}
async delete() {
return this.#client.delete(this.#name);
}
async explain(query) {
return this.#client.explain(this.#name, query);
}
async profile(query) {
return this.#client.profile(this.#name, query);
}
async memoryUsage(options) {
return this.#client.memoryUsage(this.#name, options);
}
async slowLog() {
return this.#client.slowLog(this.#name);
}
async constraintCreate(constraintType, entityType, label, ...properties) {
return this.#client.constraintCreate(this.#name, constraintType, entityType, label, ...properties);
}
async constraintDrop(constraintType, entityType, label, ...properties) {
return this.#client.constraintDrop(this.#name, constraintType, entityType, label, ...properties);
}
async copy(destGraph) {
return this.#client.copy(this.#name, destGraph);
}
#setMetadataPromise;
#updateMetadata() {
this.#setMetadataPromise ??= this.#setMetadata().finally(() => (this.#setMetadataPromise = undefined));
return this.#setMetadataPromise;
}
// DO NOT use directly, use #updateMetadata instead
async #setMetadata() {
const [labels, relationshipTypes, propertyKeys] = await Promise.all([
this.#client.roQuery(this.#name, "CALL db.labels()", undefined, false),
this.#client.roQuery(this.#name, "CALL db.relationshipTypes()", undefined, false),
this.#client.roQuery(this.#name, "CALL db.propertyKeys()", undefined, false),
]);
this.#metadata = {
labels: this.#cleanMetadataArray(labels.data),
relationshipTypes: this.#cleanMetadataArray(relationshipTypes.data),
propertyKeys: this.#cleanMetadataArray(propertyKeys.data),
};
return this.#metadata;
}
#cleanMetadataArray(arr) {
return arr.map(([value]) => value);
}
#getMetadata(key, id) {
return this.#metadata?.[key][id] ?? this.#getMetadataAsync(key, id);
}
// DO NOT use directly, use #getMetadata instead
async #getMetadataAsync(key, id) {
const value = (await this.#updateMetadata())[key][id];
if (value === undefined)
throw new Error(`Cannot find value from ${key}[${id}]`);
return value;
}
async #parseReply(reply) {
if (!reply.data)
return reply;
const promises = [], parsed = {
metadata: reply.metadata,
data: reply.data.map((row) => {
const data = {};
if (Array.isArray(row)) {
for (let i = 0; i < row.length; i++) {
const value = row[i];
data[reply.headers[i][1]] = this.#parseValue(value, promises);
}
}
return data;
}),
};
if (promises.length)
await Promise.all(promises);
return parsed;
}
#parseValue([valueType, value], promises) {
switch (valueType) {
case GraphValueTypes.NULL:
return null;
case GraphValueTypes.STRING:
case GraphValueTypes.INTEGER:
return value;
case GraphValueTypes.BOOLEAN:
return value === "true";
case GraphValueTypes.DOUBLE:
return parseFloat(value);
case GraphValueTypes.ARRAY:
return value.map((x) => this.#parseValue(x, promises));
case GraphValueTypes.EDGE:
return this.#parseEdge(value, promises);
case GraphValueTypes.NODE:
return this.#parseNode(value, promises);
case GraphValueTypes.PATH:
return {
nodes: value[0][1].map(([, node]) => this.#parseNode(node, promises)),
edges: value[1][1].map(([, edge]) => this.#parseEdge(edge, promises)),
};
case GraphValueTypes.MAP: {
const map = {};
for (let i = 0; i < value.length; i++) {
map[value[i++]] = this.#parseValue(value[i], promises);
}
return map;
}
case GraphValueTypes.POINT:
return {
latitude: parseFloat(value[0]),
longitude: parseFloat(value[1]),
};
case GraphValueTypes.VECTORF32:
return value.map((x) => Number(x));
case GraphValueTypes.DATETIME:
return polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000)
.toZonedDateTimeISO("UTC")
.toPlainDateTime();
case GraphValueTypes.DATE:
return polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000)
.toZonedDateTimeISO("UTC")
.toPlainDate();
case GraphValueTypes.TIME:
return polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000)
.toZonedDateTimeISO("UTC")
.toPlainTime();
case GraphValueTypes.DURATION:
const time = polyfill_1.Temporal.Instant.fromEpochMilliseconds(value * 1000);
const epoch = polyfill_1.Temporal.Instant.fromEpochMilliseconds(0);
return epoch
.toZonedDateTimeISO("UTC")
.until(time.toZonedDateTimeISO("UTC"), {
largestUnit: "years",
});
default:
throw new Error(`unknown scalar type: ${valueType}`);
}
}
#parseEdge([id, relationshipTypeId, sourceId, destinationId, properties,], promises) {
const edge = {
id,
sourceId,
destinationId,
properties: this.#parseProperties(properties, promises),
};
const relationshipType = this.#getMetadata("relationshipTypes", relationshipTypeId);
if (relationshipType instanceof Promise) {
promises.push(relationshipType.then((value) => (edge.relationshipType = value)));
}
else {
edge.relationshipType = relationshipType;
}
return edge;
}
#parseNode([id, labelIds, properties], promises) {
const labels = new Array(labelIds.length);
for (let i = 0; i < labelIds.length; i++) {
const value = this.#getMetadata("labels", labelIds[i]);
if (value instanceof Promise) {
promises.push(value.then((value) => (labels[i] = value)));
}
else {
labels[i] = value;
}
}
return {
id,
labels,
properties: this.#parseProperties(properties, promises),
};
}
#parseProperties(raw, promises) {
const parsed = {};
for (const [id, type, value] of raw) {
const parsedValue = this.#parseValue([type, value], promises), key = this.#getMetadata("propertyKeys", id);
if (key instanceof Promise) {
promises.push(key.then((key) => (parsed[key] = parsedValue)));
}
else {
parsed[key] = parsedValue;
}
}
return parsed;
}
async createTypedIndex(idxType, entityType, label, properties, options) {
const pattern = entityType === "NODE" ? `(e:${label})` : `()-[e:${label}]->()`;
if (idxType === "RANGE") {
idxType = "";
}
let query = `CREATE ${idxType ? idxType + " " : ""}INDEX FOR ${pattern} ON (${properties
.map((prop) => `e.${prop}`)
.join(", ")})`;
if (options) {
const optionsMap = Object.entries(options)
.map(([key, value]) => typeof value === "string" ? `${key}:'${value}'` : `${key}:${value}`)
.join(", ");
query += ` OPTIONS {${optionsMap}}`;
}
return this.#client.query(this.#name, query);
}
async createNodeRangeIndex(label, ...properties) {
return this.createTypedIndex("RANGE", "NODE", label, properties);
}
async createNodeFulltextIndex(label, ...properties) {
return this.createTypedIndex("FULLTEXT", "NODE", label, properties);
}
async createNodeVectorIndex(label, dim = 0, similarityFunction = "euclidean", ...properties) {
const options = {
dimension: dim,
similarityFunction: similarityFunction,
};
return await this.createTypedIndex("VECTOR", "NODE", label, properties, options);
}
async createEdgeRangeIndex(label, ...properties) {
return this.createTypedIndex("RANGE", "EDGE", label, properties);
}
async createEdgeFulltextIndex(label, ...properties) {
return this.createTypedIndex("FULLTEXT", "EDGE", label, properties);
}
async createEdgeVectorIndex(label, dim = 0, similarityFunction = "euclidean", ...properties) {
const options = {
dimension: dim,
similarityFunction: similarityFunction,
};
return await this.createTypedIndex("VECTOR", "EDGE", label, properties, options);
}
async dropTypedIndex(idxType, entityType, label, attribute) {
const pattern = entityType === "NODE" ? `(e:${label})` : `()-[e:${label}]->()`;
if (idxType === "RANGE") {
idxType = "";
}
let query = `DROP ${idxType ? idxType + " " : ""}INDEX FOR ${pattern} ON (e.${attribute})`;
return this.#client.query(this.#name, query);
}
async dropNodeRangeIndex(label, attribute) {
return this.dropTypedIndex("RANGE", "NODE", label, attribute);
}
async dropNodeFulltextIndex(label, attribute) {
return this.dropTypedIndex("FULLTEXT", "NODE", label, attribute);
}
async dropNodeVectorIndex(label, attribute) {
return this.dropTypedIndex("VECTOR", "NODE", label, attribute);
}
async dropEdgeRangeIndex(label, attribute) {
return this.dropTypedIndex("RANGE", "EDGE", label, attribute);
}
async dropEdgeFulltextIndex(label, attribute) {
return this.dropTypedIndex("FULLTEXT", "EDGE", label, attribute);
}
async dropEdgeVectorIndex(label, attribute) {
return this.dropTypedIndex("VECTOR", "EDGE", label, attribute);
}
}
exports.default = Graph;