UNPKG

@neo4j/graphql

Version:

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations

138 lines 5.87 kB
"use strict"; /* * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CDCApi = void 0; const cypher_builder_1 = __importDefault(require("@neo4j/cypher-builder")); const neo4j_driver_1 = require("neo4j-driver"); const error_has_gql_status_1 = require("../../../utils/error-has-gql-status"); class CDCApi { constructor(driver, queryConfig) { this.cursor = ""; this.driver = driver; this.queryConfig = queryConfig; } /** Queries events since last call to queryEvents */ async queryEvents(labels, txFilter) { if (!this.cursor) { // Resets cursor if it doesn't exists await this.refreshCursor(); } const cdcSelectors = this.createQuerySelectors(labels); if (txFilter) { cdcSelectors.map((selector) => { selector.set("txMetadata", txFilter); }); } const cdcQueryProcedure = this.createCDCQuery(this.cursor, cdcSelectors); try { const events = await this.runProcedure(cdcQueryProcedure); const firstEventOrNull = events[0]; if (!firstEventOrNull) { throw new Error("No records found on CDC transaction, this is likely a problem with the GraphQL library, please reach support"); } // If no events are returned, update the cursor id wit hthe current change ID to avoid a stale cursor if (firstEventOrNull.eventCount.toInt() === 0) { this.cursor = firstEventOrNull.currentId; return []; } this.updateChangeIdWithLastEvent(events); return events; } catch (err) { if (err instanceof neo4j_driver_1.Neo4jError) { // Cursor is stale, needs to be reset // Events between this error and the next poll will be lost if ((0, error_has_gql_status_1.errorHasGQLStatus)(err, "52N27") || (0, error_has_gql_status_1.errorHasGQLStatus)(err, "52N28") || (0, error_has_gql_status_1.errorHasGQLStatus)(err, "52N29") || (0, error_has_gql_status_1.errorHasGQLStatus)(err, "52N30") || err.code === "Neo.ClientError.ChangeDataCapture.InvalidIdentifier") { console.warn(err); await this.refreshCursor(); return []; } } throw err; } } async refreshCursor() { this.cursor = await this.fetchCurrentChangeId(); } async fetchCurrentChangeId() { const currentProcedure = cypher_builder_1.default.db.cdc.current(); const result = await this.runProcedure(currentProcedure); if (result[0] && result[0].id) { return result[0].id; } else { throw new Error("id not available on cdc.current"); } } createCDCQuery(cursor, cdcSelectors) { const cursorLiteral = new cypher_builder_1.default.Literal(cursor); const currentId = new cypher_builder_1.default.NamedVariable("currentId"); const changeId = new cypher_builder_1.default.NamedVariable("id"); const event = new cypher_builder_1.default.NamedVariable("event"); const metadata = new cypher_builder_1.default.NamedVariable("metadata"); const txId = new cypher_builder_1.default.NamedVariable("txId"); const seq = new cypher_builder_1.default.NamedVariable("seq"); return cypher_builder_1.default.utils.concat(cypher_builder_1.default.db.cdc.current().yield(["id", currentId]), cypher_builder_1.default.db.cdc .query(cursorLiteral, cdcSelectors) .optional() .yield(["id", changeId], "txId", "seq", "event", "metadata") .with("*") .orderBy([txId, "ASC"], [seq, "ASC"]) .return(currentId, changeId, event, metadata, [cypher_builder_1.default.count(changeId), "eventCount"])); } updateChangeIdWithLastEvent(events) { const lastEvent = events[events.length - 1]; if (lastEvent) { this.cursor = lastEvent.id; } } createQuerySelectors(labels) { if (labels) { return labels.map((l) => new cypher_builder_1.default.Map({ select: new cypher_builder_1.default.Literal("n"), labels: new cypher_builder_1.default.Literal([l]), })); } else { // Filters nodes return [ new cypher_builder_1.default.Map({ select: new cypher_builder_1.default.Literal("n"), }), ]; } } async runProcedure(procedure) { const { cypher, params } = procedure.build(); const result = await this.driver.executeQuery(cypher, params, this.queryConfig); return result.records.map((record) => { return record.toObject(); }); } } exports.CDCApi = CDCApi; //# sourceMappingURL=cdc-api.js.map