No Description

README.md 3.8KB

merge-by-template

A library for generating custom deep-merging functions, that merge data structures according to a set of (nested) rules that you specify.

This module is still a work-in-progress! It's already usable in production code, but the documentation is still rough, it may contain the occasional bug, and its API might still change in the future.

Example

A runnable version of this example (and more examples) can be found in example.js in this project's repository.

First, you define a merging template, which results in a custom merging function:

const mergeByTemplate = require("merge-by-template");

let mergeConfiguration = mergeByTemplate.createMerger({
	/* `null` makes it explicit that this property should be overridden as a single value,
	   despite being an object - but leaving the property out entirely would have had the
	   same result, so this is strictly for readability */
	database: null,
	scripts: {},
	accessList: [],
	powerLevel: (a, b) => a + b
});

Then, you use that with two or more input values (eg. objects):

let defaultConfiguration = {
	database: {
		type: "socket",
		path: "/default"
	},
	scripts: {
		test: "echo 'no test configured'",
		publish: "npm publish"
	},
	accessList: [
		"maintainer-bot"
	],
	powerLevel: 8999
};

let customConfiguration = {
	database: {
		hostname: "localhost",
		port: "1234",
		username: "hello",
		password: "world"
	},
	scripts: {
		test: "node test.js",
		build: "node build.js"
	},
	accessList: [
		"real-person"
	],
	powerLevel: 2
};

console.log(mergeConfiguration([ defaultConfiguration, customConfiguration ]));

... and the result of that is a value that's been deep-merged according to your specifications:

{
  database: {
    hostname: 'localhost',
    port: '1234',
    username: 'hello',
    password: 'world'
  },
  scripts: {
    test: 'node test.js',
    publish: 'npm publish',
    build: 'node build.js'
  },
  accessList: [ 'maintainer-bot', 'real-person' ],
  powerLevel: 9001
}

Rules

This section will be expanded in the future.

The basic principle: Regardless of how many values you pass into the custom merging function, it will always merge them per 2. So if you pass in [ a, b, c ] then it will first merge b onto a, and then merge c onto the result of the b -> a merger. The B side always takes precedence in the default merging strategies.

Rules can be nested to any depth (until you hit the runtime's stack size limit, anyway). This allows merging complex nested data structures.

For now, a quick listing of rule syntax:

  • No rule specified (or explicit null or undefined specified): One value overrides the other in full, even if that value is an object or array.
  • Object specified: The input values are expected to be plain objects, and each property will be merged/overridden individually.
    • Empty object: This means all properties are merged according to "No rule specified", ie. the value of one object's property overrides the other.
    • Object with rules: Each specified property is merged according to whatever rule syntax is used for that property. Unspecified properties are overridden according to "No rule specified".
  • Empty array specified: The input values are expected to be arrays, and they will be concatenated together.
  • Array with items specified: Each item is treated as a positional rule. So if you specify an array with two rules, the first item of each input array gets merged according to the first rule, the second item of each input array according to the second rule, and so on. Any surplus items for which no rule exists, are overridden according to "No rule specified".
  • Function specified: The function is called with (a, b) as arguments, and is expected to return whatever the merge result should be. This lets you implement any custom merging logic, at any level in the data structure.