You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
7.3 KiB
192 lines
7.3 KiB
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.walker = exports.generateReferentialEqualityAnnotations = exports.applyReferentialEqualityAnnotations = exports.applyValueAnnotations = void 0;
|
|
const is_js_1 = require("./is.cjs");
|
|
const pathstringifier_js_1 = require("./pathstringifier.cjs");
|
|
const transformer_js_1 = require("./transformer.cjs");
|
|
const util_js_1 = require("./util.cjs");
|
|
const pathstringifier_js_2 = require("./pathstringifier.cjs");
|
|
const accessDeep_js_1 = require("./accessDeep.cjs");
|
|
const enableLegacyPaths = (version) => version < 1;
|
|
function traverse(tree, walker, version, origin = []) {
|
|
if (!tree) {
|
|
return;
|
|
}
|
|
const legacyPaths = enableLegacyPaths(version);
|
|
if (!is_js_1.isArray(tree)) {
|
|
util_js_1.forEach(tree, (subtree, key) => traverse(subtree, walker, version, [
|
|
...origin,
|
|
...pathstringifier_js_2.parsePath(key, legacyPaths),
|
|
]));
|
|
return;
|
|
}
|
|
const [nodeValue, children] = tree;
|
|
if (children) {
|
|
util_js_1.forEach(children, (child, key) => {
|
|
traverse(child, walker, version, [
|
|
...origin,
|
|
...pathstringifier_js_2.parsePath(key, legacyPaths),
|
|
]);
|
|
});
|
|
}
|
|
walker(nodeValue, origin);
|
|
}
|
|
function applyValueAnnotations(plain, annotations, version, superJson) {
|
|
traverse(annotations, (type, path) => {
|
|
plain = accessDeep_js_1.setDeep(plain, path, v => transformer_js_1.untransformValue(v, type, superJson));
|
|
}, version);
|
|
return plain;
|
|
}
|
|
exports.applyValueAnnotations = applyValueAnnotations;
|
|
function applyReferentialEqualityAnnotations(plain, annotations, version) {
|
|
const legacyPaths = enableLegacyPaths(version);
|
|
function apply(identicalPaths, path) {
|
|
const object = accessDeep_js_1.getDeep(plain, pathstringifier_js_2.parsePath(path, legacyPaths));
|
|
identicalPaths
|
|
.map(path => pathstringifier_js_2.parsePath(path, legacyPaths))
|
|
.forEach(identicalObjectPath => {
|
|
plain = accessDeep_js_1.setDeep(plain, identicalObjectPath, () => object);
|
|
});
|
|
}
|
|
if (is_js_1.isArray(annotations)) {
|
|
const [root, other] = annotations;
|
|
root.forEach(identicalPath => {
|
|
plain = accessDeep_js_1.setDeep(plain, pathstringifier_js_2.parsePath(identicalPath, legacyPaths), () => plain);
|
|
});
|
|
if (other) {
|
|
util_js_1.forEach(other, apply);
|
|
}
|
|
}
|
|
else {
|
|
util_js_1.forEach(annotations, apply);
|
|
}
|
|
return plain;
|
|
}
|
|
exports.applyReferentialEqualityAnnotations = applyReferentialEqualityAnnotations;
|
|
const isDeep = (object, superJson) => is_js_1.isPlainObject(object) ||
|
|
is_js_1.isArray(object) ||
|
|
is_js_1.isMap(object) ||
|
|
is_js_1.isSet(object) ||
|
|
is_js_1.isError(object) ||
|
|
transformer_js_1.isInstanceOfRegisteredClass(object, superJson);
|
|
function addIdentity(object, path, identities) {
|
|
const existingSet = identities.get(object);
|
|
if (existingSet) {
|
|
existingSet.push(path);
|
|
}
|
|
else {
|
|
identities.set(object, [path]);
|
|
}
|
|
}
|
|
function generateReferentialEqualityAnnotations(identitites, dedupe) {
|
|
const result = {};
|
|
let rootEqualityPaths = undefined;
|
|
identitites.forEach(paths => {
|
|
if (paths.length <= 1) {
|
|
return;
|
|
}
|
|
// if we're not deduping, all of these objects continue existing.
|
|
// putting the shortest path first makes it easier to parse for humans
|
|
// if we're deduping though, only the first entry will still exist, so we can't do this optimisation.
|
|
if (!dedupe) {
|
|
paths = paths
|
|
.map(path => path.map(String))
|
|
.sort((a, b) => a.length - b.length);
|
|
}
|
|
const [representativePath, ...identicalPaths] = paths;
|
|
if (representativePath.length === 0) {
|
|
rootEqualityPaths = identicalPaths.map(pathstringifier_js_1.stringifyPath);
|
|
}
|
|
else {
|
|
result[pathstringifier_js_1.stringifyPath(representativePath)] = identicalPaths.map(pathstringifier_js_1.stringifyPath);
|
|
}
|
|
});
|
|
if (rootEqualityPaths) {
|
|
if (is_js_1.isEmptyObject(result)) {
|
|
return [rootEqualityPaths];
|
|
}
|
|
else {
|
|
return [rootEqualityPaths, result];
|
|
}
|
|
}
|
|
else {
|
|
return is_js_1.isEmptyObject(result) ? undefined : result;
|
|
}
|
|
}
|
|
exports.generateReferentialEqualityAnnotations = generateReferentialEqualityAnnotations;
|
|
const walker = (object, identities, superJson, dedupe, path = [], objectsInThisPath = [], seenObjects = new Map()) => {
|
|
const primitive = is_js_1.isPrimitive(object);
|
|
if (!primitive) {
|
|
addIdentity(object, path, identities);
|
|
const seen = seenObjects.get(object);
|
|
if (seen) {
|
|
// short-circuit result if we've seen this object before
|
|
return dedupe
|
|
? {
|
|
transformedValue: null,
|
|
}
|
|
: seen;
|
|
}
|
|
}
|
|
if (!isDeep(object, superJson)) {
|
|
const transformed = transformer_js_1.transformValue(object, superJson);
|
|
const result = transformed
|
|
? {
|
|
transformedValue: transformed.value,
|
|
annotations: [transformed.type],
|
|
}
|
|
: {
|
|
transformedValue: object,
|
|
};
|
|
if (!primitive) {
|
|
seenObjects.set(object, result);
|
|
}
|
|
return result;
|
|
}
|
|
if (util_js_1.includes(objectsInThisPath, object)) {
|
|
// prevent circular references
|
|
return {
|
|
transformedValue: null,
|
|
};
|
|
}
|
|
const transformationResult = transformer_js_1.transformValue(object, superJson);
|
|
const transformed = transformationResult?.value ?? object;
|
|
const transformedValue = is_js_1.isArray(transformed) ? [] : {};
|
|
const innerAnnotations = {};
|
|
util_js_1.forEach(transformed, (value, index) => {
|
|
if (index === '__proto__' ||
|
|
index === 'constructor' ||
|
|
index === 'prototype') {
|
|
throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
|
|
}
|
|
const recursiveResult = exports.walker(value, identities, superJson, dedupe, [...path, index], [...objectsInThisPath, object], seenObjects);
|
|
transformedValue[index] = recursiveResult.transformedValue;
|
|
if (is_js_1.isArray(recursiveResult.annotations)) {
|
|
innerAnnotations[pathstringifier_js_1.escapeKey(index)] = recursiveResult.annotations;
|
|
}
|
|
else if (is_js_1.isPlainObject(recursiveResult.annotations)) {
|
|
util_js_1.forEach(recursiveResult.annotations, (tree, key) => {
|
|
innerAnnotations[pathstringifier_js_1.escapeKey(index) + '.' + key] = tree;
|
|
});
|
|
}
|
|
});
|
|
const result = is_js_1.isEmptyObject(innerAnnotations)
|
|
? {
|
|
transformedValue,
|
|
annotations: !!transformationResult
|
|
? [transformationResult.type]
|
|
: undefined,
|
|
}
|
|
: {
|
|
transformedValue,
|
|
annotations: !!transformationResult
|
|
? [transformationResult.type, innerAnnotations]
|
|
: innerAnnotations,
|
|
};
|
|
if (!primitive) {
|
|
seenObjects.set(object, result);
|
|
}
|
|
return result;
|
|
};
|
|
exports.walker = walker;
|
|
//# sourceMappingURL=plainer.js.map
|