{"id":13447531,"url":"https://github.com/gullerya/object-observer","last_synced_at":"2025-04-08T00:39:42.384Z","repository":{"id":39717559,"uuid":"55340768","full_name":"gullerya/object-observer","owner":"gullerya","description":"Object Observer functionality of JavaScript objects/arrays via native Proxy","archived":false,"fork":false,"pushed_at":"2025-02-14T12:54:42.000Z","size":1180,"stargazers_count":181,"open_issues_count":2,"forks_count":21,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-31T23:37:20.637Z","etag":null,"topics":["async","atomic-changes","callback","mutations-supported","object-observer","observable","observer-pattern"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gullerya.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security.md","support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"custom":"https://paypal.me/gullerya","tidelift":"npm/object-observer"}},"created_at":"2016-04-03T10:18:19.000Z","updated_at":"2025-02-14T12:54:46.000Z","dependencies_parsed_at":"2024-01-18T15:59:04.350Z","dependency_job_id":"72884ebc-c407-49e2-b792-3b8750fa68e6","html_url":"https://github.com/gullerya/object-observer","commit_stats":{"total_commits":600,"total_committers":28,"mean_commits":"21.428571428571427","dds":0.06166666666666665,"last_synced_commit":"24999de8f415aadaaf717e3984b82bac6361476f"},"previous_names":[],"tags_count":53,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gullerya%2Fobject-observer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gullerya%2Fobject-observer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gullerya%2Fobject-observer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gullerya%2Fobject-observer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gullerya","download_url":"https://codeload.github.com/gullerya/object-observer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247755560,"owners_count":20990620,"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":["async","atomic-changes","callback","mutations-supported","object-observer","observable","observer-pattern"],"created_at":"2024-07-31T05:01:20.226Z","updated_at":"2025-04-08T00:39:42.363Z","avatar_url":"https://github.com/gullerya.png","language":"JavaScript","funding_links":["https://paypal.me/gullerya","https://tidelift.com/funding/github/npm/object-observer"],"categories":["JavaScript"],"sub_categories":[],"readme":"[![npm](https://img.shields.io/npm/v/@gullerya/object-observer.svg?label=npm)](https://www.npmjs.com/package/@gullerya/object-observer)\n[![GitHub](https://img.shields.io/github/license/gullerya/object-observer.svg)](https://github.com/gullerya/object-observer)\n\n[![Quality pipeline](https://github.com/gullerya/object-observer/actions/workflows/quality.yml/badge.svg)](https://github.com/gullerya/object-observer/actions/workflows/quality.yml)\n[![Codecov](https://img.shields.io/codecov/c/github/gullerya/object-observer/main.svg)](https://codecov.io/gh/gullerya/object-observer/branch/main)\n[![Codacy](https://img.shields.io/codacy/grade/a3879d7077eb4eef83a591733ad7c579.svg?logo=codacy)](https://www.codacy.com/app/gullerya/object-observer)\n\n# `object-observer`\n\n__`object-observer`__ provides a deep observation of a changes performed on an object/array graph.\n\nMain aspects and features:\n- implemented via native __Proxy__ (revokable)\n- observation is 'deep', yielding changes from a __sub-graphs__ too\n- nested objects of the observable graph are observables too\n- changes delivered in a __synchronous__ way by default, __asynchronous__ delivery is optionally available as per `Observable` configuration; [more details here](docs/sync-async.md)\n- observed path may optionally be filtered as per `observer` configuration; [more details here](docs/filter-paths.md)\n- original objects are __cloned__ while turned into `Observable`s\n  - circular references are nullified in the clone\n- __array__ specifics:\n  - generic object-like mutations supported\n  - intrinsic `Array` mutation methods supported: `pop`, `push`, `shift`, `unshift`, `reverse`, `sort`, `fill`, `splice`, `copyWithin`\n  - massive mutations delivered in a single callback, usually having an array of an atomic changes\n- __typed array__ specifics:\n  - generic object-like mutations supported\n  - intrinsic `TypedArray` mutation methods supported: `reverse`, `sort`, `fill`, `set`, `copyWithin`\n  - massive mutations delivered in a single callback, usually having an array of an atomic changes\n- intrinsic mutation methods of `Map`, `WeakMap`, `Set`, `WeakSet` (`set`, `delete`) etc __are not__ observed (see this [issue](https://github.com/gullerya/object-observer/issues/1) for more details)\n- following host objects (and their extensions) are __skipped__ from cloning / turning into observables: `Date`\n\nSupported:\n![CHROME](docs/browser-icons/chrome.png)\u003csub\u003e71+\u003c/sub\u003e |\n![FIREFOX](docs/browser-icons/firefox.png)\u003csub\u003e65+\u003c/sub\u003e |\n![EDGE](docs/browser-icons/edge-chromium.png)\u003csub\u003e79+\u003c/sub\u003e |\n![SAFARI](docs/browser-icons/safari-ios.png)\u003csub\u003e12.1\u003c/sub\u003e |\n![NODE JS](docs/browser-icons/nodejs.png) \u003csub\u003e12.0.0+\u003c/sub\u003e\n\nPerformance report can be found [here](docs/performance-report.md).\n\nChangelog is [here](docs/changelog.md).\n\n## Preview\n\nFor a preview/playground you are welcome to:\n- [CodePen](https://codepen.io/gullerya/pen/zYrGMNB) - `Observable.from()` flavor\n- [CodePen](https://codepen.io/gullerya/pen/WNRLJWY) - `new ObjectObserver()` flavor\n\n## Install\n\nUse regular `npm install @gullerya/object-observer --save-prod` to use the library from your local environment.\n\n__ES__ module:\n```js\nimport { Observable } from '@gullerya/object-observer';\n```\n\n__CJS__ flavor:\n```js\nconst { Observable } = require('@gullerya/object-observer');\n```\n\u003e Huge thanks to [seidelmartin](https://github.com/seidelmartin) providing the CJS build while greatly improving the build code overall along the way!\n\n__CDN__ (most suggested, when possible):\n```js\nimport { Observable } from 'https://libs.gullerya.com/object-observer/x.y.z/object-observer.min.js';\n```\n\n\u003e Replace the `x.y.z` with the desired version, one of the listed in the [changelog](docs/changelog.md).\n\nCDN features:\n- security:\n  - __HTTPS__ only\n  - __intergrity__ checksums for SRI\n- performance\n  - highly __available__ (with many geo spread edges)\n  - agressive __caching__ setup\n\nFull details about CDN usage and example are [found here](docs/cdn.md).\n\n## API\n\nLibrary implements `Observable` API as it is defined [here](docs/observable.md).\n\nThere is also a 'DOM-like' API flavor - constructable `ObjectObserver`.\nThis API is resonating with DOM's `MutationObserver`, `ResizeObserver` etc from the syntax perspective.\nUnder the hood it uses the same `Observable` mechanics.\nRead docs about this API flavor [here](docs/dom-like-api.md).\n\n`object-observer` is cross-instance operable.\nObservables created by different instances of the library will still be detected correctly as such and handled correctly by any of the instances.\n\n## Security\n\nSecurity policy is described [here](https://github.com/gullerya/object-observer/blob/main/docs/security.md). If/when any concern raised, please follow the process.\n\n## Examples\n\n##### Objects\n\n```javascript\nconst\n    order = { type: 'book', pid: 102, ammount: 5, remark: 'remove me' },\n    observableOrder = Observable.from(order);\n\nObservable.observe(observableOrder, changes =\u003e {\n    changes.forEach(change =\u003e {\n        console.log(change);\n    });\n});\n\n\nobservableOrder.ammount = 7;\n//  { type: 'update', path: ['ammount'], value: 7, oldValue: 5, object: observableOrder }\n\n\nobservableOrder.address = {\n    street: 'Str 75',\n    apt: 29\n};\n//  { type: \"insert\", path: ['address'], value: { ... }, object: observableOrder }\n\n\nobservableOrder.address.apt = 30;\n//  { type: \"update\", path: ['address','apt'], value: 30, oldValue: 29, object: observableOrder.address }\n\n\ndelete observableOrder.remark;\n//  { type: \"delete\", path: ['remark'], oldValue: 'remove me', object: observableOrder }\n\nObject.assign(observableOrder, { amount: 1, remark: 'less is more' }, { async: true });\n//  - by default the changes below would be delivered in a separate callback\n//  - due to async use, they are delivered as a batch in a single callback\n//  { type: 'update', path: ['ammount'], value: 1, oldValue: 7, object: observableOrder }\n//  { type: 'insert', path: ['remark'], value: 'less is more', object: observableOrder }\n```\n\n##### Arrays\n\n```javascript\nlet a = [ 1, 2, 3, 4, 5 ],\n    observableA = Observable.from(a);\n\nObservable.observe(observableA, changes =\u003e {\n    changes.forEach(change =\u003e {\n        console.log(change);\n    });\n});\n\n\n//  observableA = [ 1, 2, 3, 4, 5 ]\nobservableA.pop();\n//  { type: 'delete', path: [4], value: undefined, oldValue: 5, object: observableA }\n\n\n//  now observableA = [ 1, 2, 3, 4 ]\n//  following operation will cause a single callback to the observer with an array of 2 changes in it)\nobservableA.push('a', 'b');\n//  { type: 'insert', path: [4], value: 'a', oldValue: undefined, object: observableA }\n//  { type: 'insert', path: [5], value: 'b', oldValue: undefined, object: observableA }\n\n\n//  now observableA = [1, 2, 3, 4, 'a', 'b']\nobservableA.shift();\n//  { type: 'delete', path: [0] value: undefined, oldValue: 1, object: observableA }\n\n\n//  now observableA = [ 2, 3, 4, 'a', 'b' ]\n//  following operation will cause a single callback to the observer with an array of 2 changes in it)\nobservableA.unshift('x', 'y');\n//  { type: 'insert', path: [0], value: 'x', oldValue: undefined, object: observableA }\n//  { type: 'insert', path: [1], value: 'y', oldValue: undefined, object: observableA }\n\n\n//  now observableA = [ 2, 3, 4, 'a', 'b' ]\nobservableA.reverse();\n//  { type: 'reverse', path: [], object: observableA } (see below and exampe of this event for nested array)\n\n\n//  now observableA = [ 'b', 'a', 4, 3, 2 ]\nobservableA.sort();\n//  { type: 'shuffle', path: [], object: observableA } (see below and exampe of this event for nested array)\n\n\n//  observableA = [ 2, 3, 4, 'a', 'b' ]\nobservableA.fill(0, 0, 1);\n//  { type: 'update', path: [0], value: 0, oldValue: 2, object: observableA }\n\n\n//  observableA = [ 0, 3, 4, 'a', 'b' ]\n//  the following operation will cause a single callback to the observer with an array of 2 changes in it)\nobservableA.splice(0, 1, 'x', 'y');\n//  { type: 'update', path: [0], value: 'x', oldValue: 0, object: observableA }\n//  { type: 'insert', path: [1], value: 'y', oldValue: undefined, object: observableA }\n\n\nlet customer = { orders: [ ... ] },\n    oCustomer = Observable.from(customer);\n\n//  sorting the orders array, pay attention to the path in the event\noCustomer.orders.sort();\n//  { type: 'shuffle', path: ['orders'], object: oCustomer.orders }\n\n\noCustomer.orders.reverse();\n//  { type: 'reverse', path: ['orders'], object: oCustomer.orders }\n```\n\n\u003e Arrays notes: Some of array operations are effectively moving/reindexing the whole array (shift, unshift, splice, reverse, sort).\nIn cases of massive changes touching presumably the whole array I took a pessimistic approach with a special non-detailed events: 'reverse' for `reverse`, 'shuffle' for `sort`. The rest of these methods I'm handling in an optimistic way delivering the changes that are directly related to the method invocation, while leaving out the implicit outcomes like reindexing of the rest of the Array.\n\n##### Observation options\n\n`object-observer` allows to filter the events delivered to each callback/listener by an optional configuration object passed to the `observe` API.\n\n\u003e In the examples below assume that `callback = changes =\u003e {...}`.\n\n```javascript\nlet user = {\n        firstName: 'Aya',\n        lastName: 'Guller',\n        address: {\n            city: 'of mountaineers',\n            street: 'of the top ridges',\n            block: 123,\n            extra: {\n                data: {}\n            }\n        }\n    },\n    oUser = Observable.from(user);\n\n//  path\n//\n//  going to observe ONLY the changes of 'firstName'\nObservable.observe(oUser, callback, {path: 'firstName'});\n\n//  going to observe ONLY the changes of 'address.city'\nObservable.observe(oUser, callback, {path: 'address.city'});\n\n//  pathsOf\n//\n//  going to observe the changes of 'address' own properties ('city', 'block') but not else\nObservable.observe(oUser, callback, {pathsOf: 'address'});\n//  here we'll be notified on changes of\n//    address.city\n//    address.extra\n\n//  pathsFrom\n//\n//  going to observe the changes from 'address' and deeper\nObservable.observe(oUser, callback, {pathsFrom: 'address'});\n//  here we'll be notified on changes of\n//    address\n//    address.city\n//    address.extra\n//    address.extra.data\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgullerya%2Fobject-observer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgullerya%2Fobject-observer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgullerya%2Fobject-observer/lists"}