{"id":13394796,"url":"https://github.com/Yomguithereal/baobab","last_synced_at":"2025-03-13T20:31:42.086Z","repository":{"id":24093406,"uuid":"27480700","full_name":"Yomguithereal/baobab","owner":"Yomguithereal","description":"JavaScript \u0026 TypeScript persistent and optionally immutable data tree with cursors.","archived":false,"fork":false,"pushed_at":"2022-05-10T23:49:10.000Z","size":1636,"stargazers_count":3148,"open_issues_count":49,"forks_count":115,"subscribers_count":70,"default_branch":"master","last_synced_at":"2024-05-02T00:56:38.684Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Yomguithereal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"Yomguithereal"}},"created_at":"2014-12-03T10:00:42.000Z","updated_at":"2024-04-24T08:12:12.000Z","dependencies_parsed_at":"2022-08-30T16:11:06.529Z","dependency_job_id":null,"html_url":"https://github.com/Yomguithereal/baobab","commit_stats":null,"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yomguithereal%2Fbaobab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yomguithereal%2Fbaobab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yomguithereal%2Fbaobab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yomguithereal%2Fbaobab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Yomguithereal","download_url":"https://codeload.github.com/Yomguithereal/baobab/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243382923,"owners_count":20282049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T17:01:31.829Z","updated_at":"2025-03-13T20:31:42.059Z","avatar_url":"https://github.com/Yomguithereal.png","language":"JavaScript","funding_links":["https://github.com/sponsors/Yomguithereal"],"categories":["JavaScript","Uncategorized","React [🔝](#readme)","Libraries"],"sub_categories":["Uncategorized","Data Structures"],"readme":"[![Build Status](https://travis-ci.org/Yomguithereal/baobab.svg)](https://travis-ci.org/Yomguithereal/baobab)\n\n# Baobab\n\n**Baobab** is a JavaScript \u0026 TypeScript [persistent](http://en.wikipedia.org/wiki/Persistent_data_structure) and [immutable](http://en.wikipedia.org/wiki/Immutable_object) (at least by default) data tree supporting cursors and enabling developers to easily navigate and monitor nested data through events.\n\nIt is mainly inspired by functional [zippers](http://clojuredocs.org/clojure.zip/zipper) (such as Clojure's ones) and by [Om](https://github.com/swannodette/om)'s cursors.\n\nIt aims at providing a centralized model holding an application's state and can be paired with **React** easily through mixins, higher order components, wrapper components or decorators (available [there](https://github.com/Yomguithereal/baobab-react)).\n\n**Fun fact**: A [Baobab](http://en.wikipedia.org/wiki/Adansonia_digitata), or *Adansonia digitata*, is a very big and magnificent African tree.\n\n## Summary\n\n* [Example](#example)\n* [Installation](#installation)\n* [Usage](#usage)\n  * [Basics](#basics)\n    * [Instantiation](#instantiation)\n    * [Cursors](#cursors)\n    * [Updates](#updates)\n      * [set](#set)\n      * [unset](#unset)\n      * [push](#push)\n      * [unshift](#unshift)\n      * [concat](#concat)\n      * [pop](#pop)\n      * [shift](#shift)\n      * [splice](#splice)\n      * [apply](#apply)\n      * [merge](#merge)\n      * [deepMerge](#deepMerge)\n    * [Events](#events)\n  * [Advanced](#advanced)\n    * [Polymorphisms](#polymorphisms)\n    * [Computed data](#computed-data-or-monkey-business)\n    * [Specialized getters](#specialized-getters)\n    * [Traversal](#traversal)\n    * [Options](#options)\n    * [History](#history)\n    * [Common pitfalls](#common-pitfalls)\n* [Philosophy](#philosophy)\n* [Migration](#migration)\n* [Contribution](#contribution)\n* [License](#license)\n\n## Example\n\n```js\nvar Baobab = require('baobab');\n\nvar tree = new Baobab({\n  palette: {\n    colors: ['yellow', 'purple'],\n    name: 'Glorious colors'\n  }\n});\n\nvar colorsCursor = tree.select('palette', 'colors');\n\ncolorsCursor.on('update', function() {\n  console.log('Selected colors have updated!');\n});\n\ncolorsCursor.push('orange');\n```\n\n## Installation\n\nIf you want to use **Baobab** with node.js or browserify/webpack etc., you can use npm.\n\n```sh\nnpm install baobab\n\n# Or if you need the latest dev version\nnpm install git+https://github.com/Yomguithereal/baobab.git\n```\n\nIf you want to use it in the browser, just include the minified script located [here](https://raw.githubusercontent.com/Yomguithereal/baobab/master/build/baobab.min.js).\n\nNote that the library comes along with its own declaration files so you can use it comfortably with TypeScript also.\n\n```html\n\u003cscript src=\"baobab.min.js\"\u003e\u003c/script\u003e\n```\n\nOr install with bower:\n\n```js\nbower install baobab\n```\n\nThe library (as a standalone) currently weighs ~8kb gzipped.\n\n## Usage\n\n### Basics\n\n#### Instantiation\n\nCreating a tree is as simple as instantiating *Baobab* with an initial data set.\n\n```js\nvar Baobab = require('baobab');\n\nvar tree = new Baobab({hello: 'world'});\n\n// Retrieving data from your tree\ntree.get();\n\u003e\u003e\u003e {hello: 'world'}\n```\n\n#### Cursors\n\nThen you can create cursors to easily access nested data in your tree and listen to changes concerning the part of the tree you selected.\n\n```js\n// Considering the following tree\nvar tree = new Baobab({\n  palette: {\n    name: 'fancy',\n    colors: ['blue', 'yellow', 'green']\n  }\n});\n\n// Creating a cursor on the palette\nvar paletteCursor = tree.select('palette');\npaletteCursor.get();\n\u003e\u003e\u003e {name: 'fancy', colors: ['blue', 'yellow', 'green']}\n\n// Creating a cursor on the palette's colors\nvar colorsCursor = tree.select('palette', 'colors');\ncolorsCursor.get();\n\u003e\u003e\u003e ['blue', 'yellow', 'green']\n\n// Creating a cursor on the palette's third color\nvar thirdColorCursor = tree.select('palette', 'colors', 2);\nthirdColorCursor.get();\n\u003e\u003e\u003e 'green'\n\n// Note that you can also perform subselections if needed\nvar colorCursor = paletteCursor.select('colors');\n```\n\n#### Updates\n\nA *baobab* tree can obviously be updated. However, one has to understand that, even if you can write the tree synchronously, `update` events won't be, at least by default, fired until next frame.\n\nIf you really need to fire an update synchronously (typically if you store a form's state within your app's state, for instance), your remain free to use the `tree.commit()` method or tweak the tree's [options](#options) to fit your needs.\n\n**Important**: Note that the tree, being a persistent data structure, will shift the references of the objects it stores in order to enable *immutable* comparisons between one version of the state and another (this is especially useful when using strategies as such as React's [pure rendering](https://facebook.github.io/react/docs/pure-render-mixin.html)).\n\n*Example*\n\n```js\nvar tree = new Baobab({hello: 'world'});\n\nvar initialState = tree.get();\ntree.set('hello', 'monde');\n\n// After asynchronous update...\nassert(initialState !== tree.get());\n```\n\n---\n\n* [tree/cursor.set](#set)\n* [tree/cursor.unset](#unset)\n* [tree/cursor.push](#push)\n* [tree/cursor.unshift](#unshift)\n* [tree/cursor.concat](#concat)\n* [tree/cursor.pop](#pop)\n* [tree/cursor.shift](#shift)\n* [tree/cursor.splice](#splice)\n* [tree/cursor.apply](#apply)\n* [tree/cursor.merge](#merge)\n* [tree/cursor.deepMerge](#deepMerge)\n\n---\n\n\u003ch5 id=\"set\"\u003etree/cursor.set\u003c/h5\u003e\n\nReplaces value at the given key or the cursor's value altogether if no value is supplied.\n\nIt will also work if you want to replace a list's item.\n\n```js\n// Replacing the cursor's value\nvar newValue = cursor.set(newValue);\n\n// Setting a precise key\nvar newValue = cursor.set('key', newValue);\n\n// Setting a nested key\nvar newValue = cursor.set(['one', 'two'], newValue);\nvar newValue = cursor.select('one', 'two').set(newValue);\nvar newValue = cursor.select('one').set('two', newValue);\n```\n\n\u003ch5 id=\"unset\"\u003etree/cursor.unset\u003c/h5\u003e\n\nUnsets the given key.\n\nIt will also work if you want to delete a list's item.\n\n```js\n// Removing data at cursor\ncursor.unset();\n\n// Removing a precise key\ncursor.unset('key');\n\n// Removing a nested key\ncursor.unset(['one', 'two']);\n```\n\n\u003ch5 id=\"push\"\u003etree/cursor.push\u003c/h5\u003e\n\nPushes a value into the selected list. This will of course fail if the selected node is not a list.\n\n```js\n// Pushing a value\nvar newList = cursor.push(newValue);\n\n// Pushing a value in the list at key\nvar newList = cursor.push('key', newValue);\n\n// Pushing into a nested path\nvar newList = cursor.push(['one', 'two'], newValue);\nvar newList = cursor.select('one', 'two').push(newValue);\nvar newList = cursor.select('one').push('two', 'world');\n```\n\n\u003ch5 id=\"unshift\"\u003etree/cursor.unshift\u003c/h5\u003e\n\nUnshifts a value into the selected list. This will of course fail if the selected node is not a list.\n\n```js\n// Unshifting a value\nvar newList = cursor.unshift(newValue);\n\n// Unshifting a value in the list at key\nvar newList = cursor.unshift('key', newValue);\n\n// Unshifting into a nested path\nvar newList = cursor.unshift(['one', 'two'], newValue);\nvar newList = cursor.select('one', 'two').unshift(newValue);\nvar newList = cursor.select('one').unshift('two', newValue);\n```\n\n\u003ch5 id=\"concat\"\u003etree/cursor.concat\u003c/h5\u003e\n\nConcatenates a list into the selected list. This will of course fail if the selected node is not a list.\n\n```js\n// Concatenating a list\nvar newList = cursor.concat(list);\n\n// Concatenating a list in the list at key\nvar newList = cursor.concat('key', list);\n\n// Concatenating into a nested path\nvar newList = cursor.concat(['one', 'two'], list);\nvar newList = cursor.select('one', 'two').concat(list);\nvar newList = cursor.select('one').concat('two', list);\n```\n\n\u003ch5 id=\"pop\"\u003etree/cursor.pop\u003c/h5\u003e\n\nRemoves the last item of the selected list. This will of course fail if the selected node is not a list.\n\n```js\n// Popping the list\nvar newList = cursor.pop();\n\n// Popping the list at key\nvar newList = cursor.pop('key');\n\n// Popping list at path\nvar newList = cursor.pop(['one', 'two']);\nvar newList = cursor.select('one', 'two').pop();\nvar newList = cursor.select('one').pop('two');\n```\n\n\u003ch5 id=\"shift\"\u003etree/cursor.shift\u003c/h5\u003e\n\nRemoves the first item of the selected list. This will of course fail if the selected node is not a list.\n\n```js\n// Shifting the list\nvar newList = cursor.shift();\n\n// Shifting the list at key\nvar newList = cursor.shift('key');\n\n// Shifting list at path\nvar newList = cursor.shift(['one', 'two']);\nvar newList = cursor.select('one', 'two').shift();\nvar newList = cursor.select('one').shift('two');\n```\n\n\u003ch5 id=\"splice\"\u003etree/cursor.splice\u003c/h5\u003e\n\nSplices the selected list. This will of course fail if the selected node is not a list.\n\nThe `splice` specifications works the same as for [`Array.prototype.splice`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/splice).\nThere is one exception though: Per specification, splice deletes no values if the `deleteCount` argument is not parseable as a number.\nThe `splice` implementation of Baobab instead throws an error, if the given `deleteCount` argument could not be parsed.\n\n```js\n// Splicing the list\nvar newList = cursor.splice([1, 1]);\n\n// Omitting the deleteCount argument makes splice delete no elements.\nvar newList = cursor.splice([1]);\n\n// Inserting an item etc.\nvar newList = cursor.splice([1, 0, 'newItem']);\nvar newList = cursor.splice([1, 0, 'newItem1', 'newItem2']);\n\n// Splicing the list at key\nvar newList = cursor.splice('key', [1, 1]);\n\n// Splicing list at path\nvar newList = cursor.splice(['one', 'two'], [1, 1]);\nvar newList = cursor.select('one', 'two').splice([1, 1]);\nvar newList = cursor.select('one').splice('two', [1, 1]);\n```\n\n\u003ch5 id=\"apply\"\u003etree/cursor.apply\u003c/h5\u003e\n\nApplies the given function to the selected value.\n\n```js\nvar inc = function(nb) {\n  return nb + 1;\n};\n\n// Applying the function\nvar newList = cursor.apply(inc);\n\n// Applying the function at key\nvar newList = cursor.apply('key', inc);\n\n// Applying the function at path\nvar newList = cursor.apply(['one', 'two'], inc);\nvar newList = cursor.select('one', 'two').apply(inc);\nvar newList = cursor.select('one').apply('two', inc);\n```\n\n\u003ch5 id=\"merge\"\u003etree/cursor.merge\u003c/h5\u003e\n\nShallow merges the selected object with another one. This will of course fail if the selected node is not an object.\n\n```js\n// Merging\nvar newList = cursor.merge({name: 'John'});\n\n// Merging at key\nvar newList = cursor.merge('key', {name: 'John'});\n\n// Merging at path\nvar newList = cursor.merge(['one', 'two'], {name: 'John'});\nvar newList = cursor.select('one', 'two').merge({name: 'John'});\nvar newList = cursor.select('one').merge('two', {name: 'John'});\n```\n\n\u003ch5 id=\"deepMerge\"\u003etree/cursor.deepMerge\u003c/h5\u003e\n\nDeep merges the selected object with another one. This will of course fail if the selected node is not an object.\n\n```js\n// Merging\nvar newList = cursor.deepMerge({user: {name: 'John'}});\n\n// Merging at key\nvar newList = cursor.deepMerge('key', {user: {name: 'John'}});\n\n// Merging at path\nvar newList = cursor.deepMerge(['one', 'two'], {user: {name: 'John'}});\nvar newList = cursor.select('one', 'two').deepMerge({user: {name: 'John'}});\nvar newList = cursor.select('one').deepMerge('two', {user: {name: 'John'}});\n```\n\n#### Events\n\nWhenever an update is committed, events are fired to notify relevant parts of the tree that data was changed so that bound elements, UI components, for instance, may update.\n\nNote however that **only** relevant cursors will be notified of a change.\n\nEvents can be bound to either the tree or cursors using the `on` method.\n\n*Example*\n\n```js\n// Considering the following tree\nvar tree = new Baobab({\n  users: {\n    john: {\n      firstname: 'John',\n      lastname: 'Silver'\n    },\n    jack: {\n      firstname: 'Jack',\n      lastname: 'Gold'\n    }\n  }\n});\n\n// And the following cursors\nvar usersCursor = tree.select('users'),\n    johnCursor = usersCursor.select('john'),\n    jackCursor = usersCursor.select('jack');\n\n// If we update both users\njohnCursor.set('firstname', 'John the third');\njackCursor.set('firstname', 'Jack the second');\n// Every cursor above will be notified of the update\n\n// But if we update only john\njohnCursor.set('firstname', 'John the third');\n// Only the users and john cursors will be notified\n```\n\n##### Tree level\n\n*update*\n\nWill fire if the tree is updated (this concerns the asynchronous updates of the tree).\n\n```js\ntree.on('update', function(e) {\n  var eventData = e.data;\n\n  console.log('Current data:', eventData.currentData);\n  console.log('Previous data:', eventData.previousData);\n  console.log('Transaction details:', eventData.transaction);\n  console.log('Affected paths', eventData.paths);\n});\n```\n\n*write*\n\nWill fire whenever the tree is written (synchronously, unlike the `update` event).\n\n```js\ntree.on('write', function(e) {\n  console.log('Affected path:', e.data.path);\n});\n```\n\n*invalid*\n\nWill fire if the `validate` function (see [options](#options)) returned an error for the current update.\n\n```js\ntree.on('invalid', function(e) {\n  console.log('Error:', e.data.error);\n});\n```\n\n*get*\n\nWill fire whenever data is accessed in the tree.\n\n```js\ntree.on('get', function(e) {\n  console.log('Path:', e.data.path);\n  console.log('Solved path:', e.data.solvedPath);\n  console.log('Target data:', e.data.data);\n});\n```\n\n*select*\n\nWill fire whenever a path is selected in the tree.\n\n```js\ntree.on('select', function(e) {\n  console.log('Path:', e.data.path);\n  console.log('Resultant cursor:', e.data.cursor);\n});\n```\n\n##### Cursor level\n\n*update*\n\nWill fire if data watched over by the cursor has updated.\n\n```js\ncursor.on('update', function(e) {\n  var eventData = e.data;\n  console.log('Current data:', eventData.currentData);\n  console.log('Previous data:', eventData.previousData);\n});\n```\n\n##### N.B.\n\nFor more information concerning **Baobab**'s event emitting, see the [emmett](https://github.com/jacomyal/emmett) library.\n\n### Advanced\n\n#### Polymorphisms\n\nIf you ever need to, know that there are many ways to select and retrieve data within a *baobab*.\n\n```js\nvar tree = new Baobab({\n  palette: {\n    name: 'fancy',\n    colors: ['blue', 'yellow', 'green'],\n    currentColor: 1,\n    items: [{id: 'one', value: 'Hey'}, {id: 'two', value: 'Ho'}]\n  }\n});\n\n// Selecting\nvar colorsCursor = tree.select('palette', 'colors');\nvar colorsCursor = tree.select(['palette', 'colors']);\nvar colorsCursor = tree.select('palette').select('colors');\n\nvar paletteCursor = tree.select('palette');\n\n// Retrieving data\ncolorsCursor.get(1);\n\u003e\u003e\u003e 'yellow'\n\npaletteCursor.get('colors', 2);\n\u003e\u003e\u003e 'green'\n\ntree.get('palette', 'colors');\ntree.get(['palette', 'colors']);\n\u003e\u003e\u003e ['blue', 'yellow', 'green']\n\n// Retrieving or selecting data by passing a function in the path\nvar complexCursor = tree.select('palette', 'colors', function(color) {\n  return color === 'green';\n});\n\ntree.get('palette', 'colors', function(color) {\n  return color === 'green';\n});\n\u003e\u003e\u003e 'green'\n\n// Retrieving or selecting data by passing a descriptor object in the path\nvar complexCursor = tree.select('palette', 'items', {id: 'one'}, 'value');\ntree.get('palette', 'items', {id: 'one'}, 'value');\n\u003e\u003e\u003e 'Hey'\n\n// Creating a blank tree\nvar blankTree = new Baobab();\n```\n\n**Note**: when using a function or a descriptor object in a path, you are not filtering but rather selecting the first matching element. (It's actually the same as using something like [lodash](https://lodash.com/docs#find)'s `_.find`).\n\n#### Computed data or \"Monkey Business\"\n\nFor convenience, **Baobab** allows you to store computed data within the tree.\n\nIt does so by letting you create \"monkeys\" that you should really consider as dynamic nodes within your tree (*v1 users*: \"monkeys\" are merely the evolution of \"facets\").\n\nAs such, while monkeys represent reduction of the current state (a filtered list used by multiple components throughout your app, for instance), they do have a physical existence within the tree.\n\nThis means that you can add / modify / move / remove monkeys from the tree at runtime and place them wherever you want.\n\nThe reason why computed data now sits within the tree itself is so that components don't need to know from which kind of data, static or computed, they must draw their dependencies and so that read/select API might stay the same across the whole library.\n\n**Example**\n\n```js\nvar monkey = Baobab.monkey;\n// Or if you hate similes and fancy naming\nvar dynamicNode = Baobab.dynamicNode;\n\n// Declarative definition syntax\nvar tree = new Baobab({\n  user: {\n    name: 'John',\n    surname: 'Smith',\n    fullname: monkey({\n      cursors: {\n        name: ['user', 'name'],\n        surname: ['user', 'surname']\n      },\n      get: function(data) {\n        return data.name + ' ' + data.surname;\n      }\n    })\n  },\n  data: {\n    messages: [\n      {from: 'John', txt: 'Hey'},\n      {from: 'Jack', txt: 'Ho'}\n    ],\n    fromJohn: monkey({\n      cursors: {\n        messages: ['data', 'messages'],\n      },\n      get: function(data) {\n        return data.messages.filter(function(m) {\n          return m.from === 'John';\n        });\n      }\n    })\n  }\n});\n\n// Alternate shorthand definition syntax\nvar tree = new Baobab({\n  user: {\n    name: 'John',\n    surname: 'Smith',\n    fullname: monkey(\n      ['user', 'name'],\n      ['user', 'surname'],\n      function(name, surname) {\n        return name + ' ' + surname;\n      }\n    )\n  },\n  data: {\n    messages: [\n      {from: 'John', txt: 'Hey'},\n      {from: 'Jack', txt: 'Ho'}\n    ],\n    fromJohn: monkey(\n      ['data', 'messages'],\n      function(messages) {\n        return messages.filter(function(m) {\n          return m.from === 'John';\n        });\n      }\n    )\n  }\n});\n\n// Possibility to disable a single monkey's immutability\nvar tree = new Baobab({\n  data: {\n    users: ['Jack', 'John'],\n    onlyJack: monkey({\n      cursors: {\n        users: ['data', 'users'],\n        get: function(data) {\n          return data.users.filter(function(user) {\n            return user === 'Jack';\n          });\n        },\n        options: {\n          immutable: false\n        }\n      }\n    }),\n\n    // Using the shorthand\n    onlyJohn: monkey(\n      ['data', 'users'],\n      function(users) {\n        return users.filter(function(user) {\n          return user === 'John';\n        });\n      },\n      {immutable: false}\n    )\n  }\n});\n\n// Finally, know that you can use relative paths for convenience\nvar tree = new Baobab({\n  data: {\n    user: {\n      name: 'John',\n      surname: 'Smith',\n      fullname: monkey(\n        ['.', 'name'],\n        ['.', 'surname'],\n        function(name, surname) {\n          return name + ' ' + surname;\n        }\n      ),\n      evenMoreNested: {\n        fullname: monkey(\n          ['..', 'name'],\n          ['..', 'surname'],\n          function(name, surname) {\n            return name + ' ' + surname;\n          }\n        )\n      }\n    }\n  }\n});\n\n// You can then access or select data naturally\ntree.get('user', 'fullname');\n\u003e\u003e\u003e 'John Smith'\n\ntree.get('data', 'fromJohn');\n\u003e\u003e\u003e [{from: 'John', txt: 'Hey'}]\n\n// You can also access/select data beneath a monkey\ntree.get('data', 'fromJohn', 'txt');\n\u003e\u003e\u003e 'Hey'\n\nvar cursor = tree.select('data', 'fromJohn', 'txt');\n\n// Just note that computed data node is read-only and that the tree\n// will throw if you try to update a path lying beyond a computed node\ntree.set(['data', 'fromJohn', 'txt'], 'Yay');\n\u003e\u003e\u003e Error!\n\n// You can add / remove / modify a monkey at runtime using the same API\ntree.set(['data', 'fromJack'], monkey({\n  cursors: {\n    messages: ['data', 'messages'],\n    function(messages) {\n      return messages.filter(function(m) {\n        return m.from === 'Jack';\n      });\n    }\n  }\n}));\n```\n\n**Notes**\n\n* The dynamic nodes will of course automatically update whenever at least one of the watched paths is updated.\n* The dynamic nodes are lazy and won't actually be computed before you get them (plus they will only compute once before they need to change, so if you get the same dynamic node twice, the computation won't rerun).\n* There are cases where it is clearly overkill to rely on a dynamic node. For instance, if only a single component of your app needs to access a computed version of the central state, then compute this version into the rendering logic of said component for simplicity's sake (a React component's render function for instance). Dynamic nodes are somewhat part of an optimization scheme.\n* Know that the `tree/cursor.serialize` method exists would you need to retrieve data stripped of dynamic nodes from your tree.\n* For the time being, placing monkeys beneath array nodes is not allowed for performance reasons.\n\n#### Specialized getters\n\n**tree/cursor.exists**\n\nCheck whether a specific path exists within the tree (won't fire a `get` event).\n\n```js\n// Probably true\ntree.exists();\n\n// Does the cursor points at an existing path?\ncursor.exists();\n\n// Can also take a path\ntree.exists('hello');\ntree.exists('hello', 'message');\ntree.exists(['hello', 'message']);\n```\n\n**tree/cursor.clone**\n\nShallow clone the cursor's data. The method takes an optional nested path.\n\n```js\nvar tree = new Baobab({user: {name: 'John'}}),\n    cursor = tree.select('user');\n\nassert(cursor.get() !== cursor.clone());\n```\n\n**tree/cursor.deepClone**\n\nSame as the `tree/cursor.clone` except that it will deep clone the data.\n\n**tree/cursor.serialize**\n\nRetrieve only raw data (therefore avoiding computed data) from the tree or a cursor.\n\nThis is useful when you want to serialize your tree into JSON, for instance.\n\n```js\ntree.serialize();\ncursor.serialize();\n\n// Can also take a path\ntree.serialize('hello');\ntree.serialize('hello', 'message');\ntree.serialize(['hello', 'message']);\n```\n\n**tree.watch**\n\nCreate a watcher that will fire an `update` event if any of the given paths is affected by a transaction.\n\nThis is useful to create modules binding a state tree to UI components.\n\n```js\n// Considering the following tree\nvar tree = new Baobab({\n  one: {\n    name: 'John'\n  },\n  two: {\n    surname: 'Smith'\n  }\n});\n\nvar watcher = tree.watch({\n  name: ['one', 'name'],\n  surname: ['two', 'surname']\n});\n\nwatcher.on('update', function(e) {\n  // One of the watched paths was updated!\n});\n\nwatcher.get();\n\u003e\u003e\u003e {\n  name: 'John',\n  surname: 'Smith'\n}\n```\n\n**tree/cursor.project**\n\nRetrieve data from several parts of the tree by following the given projection:\n\n```js\n// Considering the following tree\nvar tree = new Baobab({\n  one: {\n    name: 'John'\n  },\n  two: {\n    surname: 'Smith'\n  }\n});\n\n// Using an object projection\ntree.project({\n  name: ['one', 'name'],\n  surname: ['two', 'surname']\n});\n\u003e\u003e\u003e {name: 'John', surname: 'Smith'}\n\n// Using an array projection\ntree.project([\n  ['one', 'name'],\n  ['two', 'surname']\n]);\n\u003e\u003e\u003e ['John', 'Smith']\n```\n\n#### Traversal\n\n*Getting root cursor*\n\n```js\nvar tree = new Baobab({first: {second: 'yeah'}}),\n    cursor = tree.select('first');\n\nvar rootCursor = tree.root;\n// or\nvar rootCursor = cursor.root();\n```\n\n*Going up in the tree*\n\n```js\nvar tree = new Baobab({first: {second: 'yeah'}})\n    secondCursor = tree.select('first', 'second');\n\nvar firstCursor = secondCursor.up();\n```\n\n*Going left/right/down in lists*\n\n```js\nvar tree = new Baobab({\n  list: [[1, 2], [3, 4]],\n  longList: ['one', 'two', 'three', 'four']\n});\n\nvar listCursor = tree.select('list'),\n    twoCursor = tree.select('longList', 1);\n\nlistCursor.down().right().get();\n\u003e\u003e\u003e [3, 4]\n\nlistCursor.select(1).down().right().get();\n\u003e\u003e\u003e 4\n\nlistCursor.select(1).down().right().left().get();\n\u003e\u003e\u003e 3\n\ntwoCursor.leftmost().get();\n\u003e\u003e\u003e 'one'\n\ntwoCursor.rightmost().get();\n\u003e\u003e\u003e 'four'\n```\n\n*Mapping cursors over a list node*\n\n```js\nvar tree = new Baobab({list: [1, 2, 3]});\n\ntree.select('list').map(function(cursor, i) {\n  console.log(cursor.get());\n});\n\u003e\u003e\u003e 1\n\u003e\u003e\u003e 2\n\u003e\u003e\u003e 3\n```\n\n*Getting information about the cursor's location in the tree*\n\n```js\ncursor.isRoot();\ncursor.isBranch();\ncursor.isLeaf();\n```\n\n#### Options\n\nYou can pass those options at instantiation.\n\n```js\nvar baobab = new Baobab(\n\n  // Initial data\n  {\n    palette: {\n      name: 'fancy',\n      colors: ['blue', 'green']\n    }\n  },\n\n  // Options\n  {\n    autoCommit: false\n  }\n)\n```\n\n* **autoCommit** *boolean* [`true`]: should the tree auto commit updates or should it let the user do so through the `commit` method?\n* **asynchronous** *boolean* [`true`]: should the tree delay the update to the next frame or fire them synchronously?\n* **immutable** *boolean* [`true`]: should the tree's data be immutable? Note that immutability is performed through `Object.freeze` and should be disabled in production for performance reasons.\n* **lazyMonkeys** *boolean* [`true`]: should the monkeys be lazy? Disable this option for easier debugging in your console (getter functions are sometimes hard to read in the console).\n* **monkeyBusiness** *boolean* [`true`]: should the tree support monkeys? Disabling this yields significant performance boost for large trees without monkeys.\n* **persistent** *boolean* [`true`]: should the tree be persistent. Know that disabling this option, while bringing a significant performance boost on heavy data, will make you lose the benefits of your tree's history and `O(1)` comparisons of objects.\n* **pure** *boolean* [`true`]: by default, on `set` and `apply` operations, the tree will check if the given value and the target node are stricly equal. If they indeed are, the tree won't update.\n* **validate** *function*: a function in charge of validating the tree whenever it updates. See below for an example of such function.\n* **validationBehavior** *string* [`rollback`]: validation behavior of the tree. If `rollback`, the tree won't apply the current update and fire an `invalid` event while `notify` will only emit the event and let the tree enter the invalid state anyway.\n\n*Validation function*\n\n```js\nfunction validationFunction(previousState, newState, affectedPaths) {\n  // Perform validation here and return an error if\n  // the tree is invalid\n  if (!valid)\n    return new Error('Invalid tree because of reasons.');\n}\n\nvar tree = new Baobab({...}, {validate: validationFunction});\n```\n\n#### History\n\n**Baobab** lets you record the successive states of any cursor so you can seamlessly implement undo/redo features.\n\n*Example*\n\n```js\n// Synchronous tree so that examples are simpler\nvar baobab = new Baobab({colors: ['blue']}, {asynchronous: false}),\n    cursor = baobab.select('colors');\n\n// Starting to record state, with 10 records maximum\ncursor.startRecording(10);\n\ncursor.push('yellow');\ncursor.push('purple');\ncursor.push('orange');\n\ncursor.get();\n\u003e\u003e\u003e ['blue', 'yellow', 'purple', 'orange']\n\ncursor.undo();\ncursor.get();\n\u003e\u003e\u003e ['blue', 'yellow', 'purple']\n\ncursor.undo(2);\ncursor.get();\n\u003e\u003e\u003e ['blue']\n```\n\n*Starting recording*\n\nIf you do not provide a maximum number of records, will record everything without any limit.\n\n```js\ncursor.startRecording(maxNbOfRecords);\n```\n\n*Stoping recording*\n\n```js\ncursor.stopRecording();\n```\n\n*Undoing*\n\n```js\ncursor.undo();\ncursor.undo(nbOfSteps);\n```\n\n*Clearing history*\n\n```js\ncursor.clearHistory();\n```\n\n*Checking if the cursor has an history*\n\n```js\ncursor.hasHistory();\n```\n\n*Retrieving the cursor's history*\n\n```js\ncursor.getHistory();\n```\n\n#### Common pitfalls\n\n**Releasing**\n\nIn most complex use cases, you might need to release the manipulated objects,\ni.e. kill their event emitters and wipe their associated data. For example,\ncursors with a dynamic path (e.g. `var cursor = tree.select(['items', { id: 5 }])`), will\nalways create a cursor with listeners that need to be released when you are done\nusing the cursor.\n\nThus, any tree or cursor object can be cleared from memory by using the `release` method.\n\n```js\ntree.release();\ncursor.release();\nwatcher.release();\n```\n\nNote also that releasing a tree will consequently and automatically release every of its cursors and computed data nodes.\n\n## Philosophy\n\n**User interfaces as pure functions**\n\nUser interfaces should be, as far as possible, considered as pure functions. Baobab is just a way to provide the needed arguments, i.e. the data representing your app's state, to such a function.\n\nConsidering your UIs like pure functions comes along with collateral advantages like easy undo/redo features, state storing (just save your tree in the `localStorage` and here you go) and easy usage in both client \u0026 server.\n\n**Only data should enter the tree**\n\nYou shouldn't try to shove anything else than raw data into the tree. The tree hasn't been conceived to hold classes or fancy indexes with many circular references and cannot perform its magic on it. But, probably such magic is not desirable for those kind of abstractions anyway.\n\nThat is to say the data you insert into the tree should logically be JSON-serializable else you might be missing the point.\n\n## Migration\n\n**From v1 to v2**\n\n* The tree is now immutable by default (but you can shunt this behavior through a dedicated [option](#options)).\n* Writing to the tree is now synchronous for convenience. Updates remain asynchronous for obvious performance reasons.\n* You cannot chain update methods now since those will return the affected node's data to better tackle immutability.\n* The strange concat-like behavior of the `push` and `unshift` method was dropped in favor of the `concat` method.\n* Facets are now full-fledged dynamic nodes called monkeys.\n* The weird `$cursor` sugar has been dropped.\n* The update specifications have been dropped.\n\n**From v0.4.x to v1**\n\nA lot of changes occurred between `0.4.x` and `1.0.0`. Most notable changes being the following ones:\n\n* The tree now shift references by default.\n* React integration has improved and is now handled by [baobab-react](https://github.com/Yomguithereal/baobab-react).\n* `cursor.edit` and `cursor.remove` have been replaced by `cursor.set` and `cursor.unset` single argument polymorphisms.\n* A lot of options (now unnecessary) have been dropped.\n* Validation is no longer handled by [`typology`](https://github.com/jacomyal/typology) so you can choose you own validation system and so the library can remain lighter.\n* Some new features such as: `$splice`, facets and so on...\n\nFor more information, see the [changelog](./CHANGELOG.md).\n\n## Contribution\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FYomguithereal%2Fbaobab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FYomguithereal%2Fbaobab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FYomguithereal%2Fbaobab/lists"}