@grouparoo/core
Version:
The Grouparoo Core
175 lines (151 loc) • 5.29 kB
text/typescript
import { Op } from "sequelize";
import { Mapping } from "../models/Mapping";
import { Property } from "../models/Property";
import { Source } from "./../models/Source";
import { Destination } from "./../models/Destination";
import { LockableHelper } from "./lockableHelper";
import { modelName } from "./modelName";
import { PropertiesCache } from "./caches/propertiesCache";
export namespace MappingHelper {
export type Mappings = Record<string, string>;
export async function getMapping(
instance: Source | Destination,
propertyColumn?: "key" | "id"
) {
const mappingObject: Mappings = {};
const mappings =
instance.mappings ??
(await Mapping.findAll({
where: {
ownerId: instance.id,
ownerType: modelName<Source | Destination>(instance),
},
}));
if (!instance.mappings) instance.mappings = mappings;
for (const i in mappings) {
const mapping = mappings[i];
const property = await PropertiesCache.findOneWithCache(
mapping.propertyId,
instance.modelId,
"ready"
);
if (!property) {
throw new Error(
`cannot find property or this source/destination not ready (remoteKey: ${mapping.remoteKey}, propertyId: ${mapping.propertyId})`
);
}
mappingObject[mapping.remoteKey] = property[propertyColumn || "key"];
}
return mappingObject;
}
export async function getConfigMapping(instance: Source | Destination) {
const mappingObject: Mappings = {};
const mappings = await Mapping.findAll({
where: { ownerId: instance.id },
include: [Property],
});
for (const mapping of mappings) {
const property = await mapping.property;
if (!property) {
throw new Error(
`cannot find property or this source/destination not ready (remoteKey: ${mapping.remoteKey})`
);
}
mappingObject[mapping.remoteKey] = property.getConfigId();
}
return mappingObject;
}
export async function setMapping(
instance: (Source | Destination) & { afterSetMapping?: Function },
mappings: Mappings,
externallyValidate = true
) {
delete instance.mappings;
await LockableHelper.beforeUpdateOptions(instance);
await Mapping.destroy({
where: {
ownerId: instance.id,
ownerType: modelName<Source | Destination>(instance),
},
});
const otherSourcePrimaryKey =
instance instanceof Source
? await Property.findOne({
include: {
model: Source,
required: true,
where: {
modelId: instance.modelId,
},
},
where: {
isPrimaryKey: true,
sourceId: {
[Op.ne]: instance.id,
},
},
})
: undefined;
let newMappings: Mapping[] = [];
const keys = Object.keys(mappings);
for (const i in keys) {
const remoteKey = keys[i];
const key = mappings[remoteKey];
const property = await PropertiesCache.findOneWithCache(
key,
undefined,
"ready",
"key"
);
if (!property) throw new Error(`cannot find property ${key}`);
if (instance instanceof Source && property.isArray) {
throw new Error(
`Sources cannot map to an array Property ${property.key} (${property.id})`
);
}
const source = await property.$get("source", { scope: null });
if (source.modelId !== instance.modelId) {
throw new Error(
`cannot map a ${instance.modelId} model to a ${source.modelId} property (${source.name})`
);
}
if (otherSourcePrimaryKey && property.sourceId === instance.id) {
throw new Error(
`'${instance.name}' cannot map '${remoteKey}' to own Property '${key}'.
'${otherSourcePrimaryKey.source.name}' is the primary Source for Model
${instance.modelId} and '${instance.name}' requires a mapping back to '${otherSourcePrimaryKey.source.name}.'`
);
}
if (externallyValidate && instance instanceof Source)
await validateRemoteKey(instance, remoteKey);
const mapping = await Mapping.create({
ownerId: instance.id,
ownerType: modelName<Source | Destination>(instance),
propertyId: property.id,
remoteKey,
});
newMappings.push(mapping);
}
instance.mappings = newMappings;
await instance.touch();
// if there's an afterSetMapping hook and we want to commit our changes
if (typeof instance["afterSetMapping"] === "function") {
await instance["afterSetMapping"]();
}
}
async function validateRemoteKey(instance: Source, remoteKey: string) {
const previewAvailable = await instance.previewAvailable();
if (!previewAvailable) return;
const sourcePreview = await instance.sourcePreview();
if (sourcePreview.length > 0) {
const validKeys = Object.keys(sourcePreview[0]);
if (!validKeys.includes(remoteKey)) {
throw new Error(
`"${remoteKey}" is not a valid remote mapping key for ${modelName(
instance
)} ${instance.name}`
);
}
}
}
}