@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
138 lines • 5.87 kB
JavaScript
/*
* 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
;