UNPKG

falkordb

Version:
298 lines (297 loc) 12.8 kB
"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;