Compare commits

...

2 Commits

@ -1,13 +1,15 @@
"use strict";
// A strict deep-merging implementation that *only* merges regular objects, and prevents prototype pollution
// A strict deep-merging implementation that *only* merges regular objects, and prevents prototype pollution, and also optionally allows specifying a value mapper (to avoid the need for a second traversal)
// FIXME: Publish this as a stand-alone package?
function isObject(value) {
// TODO: Disallow special object types, for statically defined values (or just disallow specifying static values in the root schema in dlayer?)
return (value != null && typeof value === "object" && !Array.isArray(value));
// FIXME: The __moduleID is a hack to prevent it from recognizing a module-association wrapper as a schema object; should find a better way to do this, that generalizes to a stand-alone package
return (value != null && typeof value === "object" && !Array.isArray(value) && value.__moduleID == null);
}
module.exports = function deepMerge(a, b) {
module.exports = function deepMergeAndMap(a, b, valueMapper = (value) => value) {
let merged = Object.create(null);
let keys = new Set([ ... Object.keys(a), ... Object.keys(b) ]);
@ -23,11 +25,14 @@ module.exports = function deepMerge(a, b) {
if (isObject(valueA) && valueB === undefined) {
merged[key] = valueA;
} else if (isObject(valueB) && valueA === undefined) {
merged[key] = valueB;
// This looks hacky, but it ensures that the behaviour (eg. value mapping) is consistent between initially-created subtrees and those that are merged in later
merged[key] = deepMergeAndMap({}, valueB, valueMapper);
} else if (isObject(valueA) && isObject(valueB)) {
merged[key] = deepMerge(valueA, valueB);
merged[key] = deepMergeAndMap(valueA, valueB, valueMapper);
} else if (!isObject(valueA) && !isObject(valueB)) {
merged[key] = valueB ?? valueA;
merged[key] = (valueB != null)
? valueMapper(valueB)
: valueA;
} else {
// FIXME: Identifiable error type, and include the error path as well
throw new Error("Cannot merge non-object into object");
@ -35,4 +40,4 @@ module.exports = function deepMerge(a, b) {
}
return merged;
};
};

@ -5,7 +5,7 @@ const mapObject = require("map-obj");
const Result = require("@joepie91/result");
const createCursor = require("./cursor");
const deepMerge = require("./deep-merge");
const deepMergeAndMap = require("./deep-merge-and-map");
const loadModules = require("./load-modules");
// TODO: Bounded/unbounded recursion
@ -226,7 +226,7 @@ module.exports = function createDLayer(options) {
// options = { schema, makeContext }
let loaded = loadModules(options.modules ?? []);
let schema = deepMerge(loaded.root, options.schema);
let schema = deepMergeAndMap(loaded.root, options.schema);
return {
query: function (query, context) {

@ -2,7 +2,7 @@
// const mergeByTemplate = require("merge-by-template");
const syncpipe = require("syncpipe");
const deepMerge = require("./deep-merge");
const deepMergeAndMap = require("./deep-merge-and-map");
/*
Take a list of modules; each module specifies a name, schema root, types, type extensions, and a context factory function. Each module is internally assigned a unique ID. This unique ID is associated with each type factory and type extension method, and used as the key for a map of context factories; that way, upon invoking those methods, the module's own corresponding context can be injected. Only a single context should be created per module per request, so there should be a cache layer for the contexts (keyed by module ID), with each request creating a new cache.
@ -92,7 +92,15 @@ module.exports = function (modules) {
_ => new Map(_)
]);
let schemaRoots = modules.map((module) => module.root ?? {});
let schema = modules.reduce((schema, module) => {
return deepMergeAndMap(schema, module.root, (value) => {
if (typeof value === "function") {
return wrapModuleFunction(module, value);
} else {
return value;
}
});
}, {});
for (let module of modules) {
for (let [ type, factory ] of Object.entries(module.types ?? {})) {
@ -107,7 +115,7 @@ module.exports = function (modules) {
}
return {
root: schemaRoots.reduce(deepMerge, {}),
root: schema,
types: types.get(),
extensions: typeExtensions.get(),
makeContextFactory: function (baseContext) {

@ -56,7 +56,7 @@ let moduleDrives = {
},
makeContext: () => {
return {
counter: contextCounter++
counter: "drives-" + contextCounter++
};
}
};
@ -99,7 +99,7 @@ let moduleBlockDevices = {
},
makeContext: () => {
return {
counter: contextCounter++
counter: "blockDevices-" + contextCounter++
};
}
};

@ -1,6 +1,6 @@
{
"name": "dlayer",
"version": "0.1.0",
"version": "0.1.1",
"main": "index.js",
"repository": "https://git.cryto.net/joepie91/dlayer.git",
"author": "Sven Slootweg <admin@cryto.net>",

Loading…
Cancel
Save