Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

lazy-recursive-merge

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lazy-recursive-merge

Deep-merge objects with lazily-called accessor functions

  • 1.0.0
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
0
decreased by-100%
Maintainers
1
Weekly downloads
 
Created
Source

lazy-recursive-merge

Allow declarative configurations from independent sources with embedded evaluation.

This happens by taking an array of objects and merging them into a new object, and then providing getter functions to call functions with the new object. This allows creating e.g. a configuration where lower-priority configurations can access the higher-priority configuration values for calculations.

Using this concept, you can merge arrays, define file paths with prefixes, conditionally enable features etc.

Example:

Given the objects

const configs = [
	{a: 'a'},
	{b: cfg => `${cfg.a}/${cfg.p}`},
	{p: 'hi'},
	{plugin: () => import('myPlugin')},
	{f: cfg => `m:${cfg.b}`, p: 'hello'},
]

the resulting configuration is

{a: 'a', b: 'a/hello', p: 'hello', plugin: /* Promise<plugin> */, f: 'm:a/hello'}

In other words, the key p in the last object was used by the key b in the second object, and the key f in the last object used the key b in turn.

The functions b, plugin and f won't be called until they are referenced, so for example myPlugin won't be loaded until config.plugin is read, returning a Promise.

This way, you can define configurations that are loosely coupled but can change any part of the final configuration programmatically. This is a useful property for pluggable systems, as evidenced by NixOS, an entire Linux distribution based on this concept.

How it works

Given an array of objects, they are merged as follows:

  • higher-array-index objects get greater priority
  • objects are merged
  • Promises cause the merge to return a Promise for the merged value
  • functions are called lazily as fn(config, {prev, path})
    • prev is the value at the same path of the previous objects
    • if the result is a Promise, it performs the rest of the merges after the Promise resolves
      • you must await the result
    • the result replaces the configuration value at that path
    • if the result is an object, it is handled recursively
      • you can return an object with functions for further evaluation
      • cycles are caught
    • if you need to represent a function foo, return it with () => foo, it won't be considered for lazy evaluation
  • anything else overrides lower priority values

API

const config = lrm(objects, {target} = {})

  • objects: array of enumerable objects (these cannot be Promises)
  • target: optional object that will get the configuration
  • returns the configuration object

The return value is the mutated target object if it was passed. This way, you can retain references to a changing configuration object.

Requirements

This only uses Object.defineProperty and WeakMap (for loop detection only), so it should work on everything with a polyfill for WeakMap.

Ideas for future work

  • opinionated config loader, in a separate package, like confippet or dotenv
    • [{env: process.env}, try_load('config/defaults'), try_load('config/defaults.${process.env.NODE_ENV}')]
  • helper for marking functions as not-accessor? fn[Symbol.for('lrm.value')]=true
  • allow custom mergers
    • need to accept {path, root} and implement .add() and .finalize()
    • subclass DefaultMerge, or perhaps a tagged factory fn: makeMerger[Symbol.for('lrm.merge')]=true
    • can enforce types via throwing, concat arrays, sort, ...
    • could be integrated with TS by converting TS interfaces to a runtime checker, or other way round
    • provide a bunch of default mergers that implement runtime type checking
  • support Proxy object to allow runtime key lookup (e.g. mapping a directory)
  • a function to find which config determined the value of a given path, for error reporting.
  • if WeakMap is not available, use a recursion depth limit
  • implement a NixPkgs clone

Keywords

FAQs

Package last updated on 15 Apr 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc