lib6/mapping.highlevel.js
/**
* 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.
*/
import { newError } from './error';
import { nameConventions } from './mapping.nameconventions';
export let rulesRegistry = {};
let nameMapping = (name) => name;
function register(constructor, rules) {
rulesRegistry[constructor.name] = rules;
}
function clearMappingRegistry() {
rulesRegistry = {};
}
function translateIdentifiers(translationFunction) {
nameMapping = translationFunction;
}
function getCaseTranslator(databaseConvention, codeConvention) {
const keys = Object.keys(nameConventions);
if (!keys.includes(databaseConvention)) {
throw newError(`Naming convention ${databaseConvention} is not recognized,
please provide a recognized name convention or manually provide a translation function.`);
}
if (!keys.includes(codeConvention)) {
throw newError(`Naming convention ${codeConvention} is not recognized,
please provide a recognized name convention or manually provide a translation function.`);
}
// @ts-expect-error
return (name) => nameConventions[databaseConvention].encode(nameConventions[codeConvention].tokenize(name));
}
export const RecordObjectMapping = Object.freeze({
/**
* Clears all registered type mappings from the record object mapping registry.
* @experimental Part of the Record Object Mapping preview feature
*/
clearMappingRegistry,
/**
* Creates a translation function from record key names to object property names, for use with the {@link translateIdentifiers} function
*
* Recognized naming conventions are "camelCase", "PascalCase", "snake_case", "kebab-case", "SCREAMING_SNAKE_CASE"
*
* @experimental Part of the Record Object Mapping preview feature
* @param {string} databaseConvention The naming convention in use in database result Records
* @param {string} codeConvention The naming convention in use in JavaScript object properties
* @returns {function} translation function
*/
getCaseTranslator,
/**
* Registers a set of {@link Rules} to be used by {@link hydrated} for the provided class when no other rules are specified. This registry exists in global memory, not the driver instance.
*
* @example
* // The following code:
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
* resultTransformer: neo4j.resultTransformers.hydrated(Person, personClassRules)
* })
*
* can instead be written:
* neo4j.RecordObjectMapping.register(Person, personClassRules)
*
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
* resultTransformer: neo4j.resultTransformers.hydrated(Person)
* })
*
* @experimental Part of the Record Object Mapping preview feature
* @param {GenericConstructor} constructor The constructor function of the class to set rules for
* @param {Rules} rules The rules to set for the provided class
*/
register,
/**
* Sets a default name translation from record keys to object properties.
* If providing a function, provide a function that maps FROM your object properties names TO record key names.
*
* The function getCaseTranslator can be used to provide a prewritten translation function between some common naming conventions.
*
* @example
* //if the keys on records from the database are in ALLCAPS
* RecordObjectMapping.translateIdentifiers((name) => name.toUpperCase())
*
* //if you utilize PacalCase in the database and camelCase in JavaScript code.
* RecordObjectMapping.translateIdentifiers(mapping.getCaseTranslator("PascalCase", "camelCase"))
*
* //if a type has one odd mapping you can override the translation with the rule
* const personRules = {
* firstName: neo4j.rule.asString(),
* bornAt: neo4j.rule.asNumber({ acceptBigInt: true, optional: true })
* weird_name-property: neo4j.rule.asString({from: 'homeTown'})
* }
* //These rules can then be used by providing them to a hydratedResultsMapper
* record.as<Person>(personRules)
* //or by registering them to the mapping registry
* RecordObjectMapping.register(Person, personRules)
*
* @experimental Part of the Record Object Mapping preview feature
* @param {function} translationFunction A function translating the names of your JS object property names to record key names
*/
translateIdentifiers
});
export function as(gettable, constructorOrRules, rules) {
const GenericConstructor = typeof constructorOrRules === 'function' ? constructorOrRules : Object;
const theRules = getRules(constructorOrRules, rules);
const visitedKeys = [];
const obj = new GenericConstructor();
for (const [key, rule] of Object.entries(theRules !== null && theRules !== void 0 ? theRules : {})) {
visitedKeys.push(key);
_apply(gettable, obj, key, rule);
}
for (const key of Object.getOwnPropertyNames(obj)) {
if (!visitedKeys.includes(key)) {
_apply(gettable, obj, key, theRules === null || theRules === void 0 ? void 0 : theRules[key]);
}
}
return obj;
}
function _apply(gettable, obj, key, rule) {
var _a;
const mappedkey = nameMapping(key);
const value = gettable.get((_a = rule === null || rule === void 0 ? void 0 : rule.from) !== null && _a !== void 0 ? _a : mappedkey);
const field = `${obj.constructor.name}#${key}`;
const processedValue = valueAs(value, field, rule);
// @ts-expect-error
obj[key] = processedValue !== null && processedValue !== void 0 ? processedValue : obj[key];
}
export function valueAs(value, field, rule) {
if ((rule === null || rule === void 0 ? void 0 : rule.optional) === true && value == null) {
return value;
}
if (typeof (rule === null || rule === void 0 ? void 0 : rule.validate) === 'function') {
rule.validate(value, field);
}
return ((rule === null || rule === void 0 ? void 0 : rule.convert) != null) ? rule.convert(value, field) : value;
}
function getRules(constructorOrRules, rules) {
const rulesDefined = typeof constructorOrRules === 'object' ? constructorOrRules : rules;
if (rulesDefined != null) {
return rulesDefined;
}
return typeof constructorOrRules !== 'object' ? rulesRegistry[constructorOrRules.name] : undefined;
}