{"id":21810485,"url":"https://github.com/alexandercerutti/roxe","last_synced_at":"2025-06-11T19:34:22.787Z","repository":{"id":96190583,"uuid":"172395175","full_name":"alexandercerutti/roxe","owner":"alexandercerutti","description":"Observe your object changes with subscribers. Rx.JS + ES Proxy = ❤","archived":false,"fork":false,"pushed_at":"2020-01-03T23:12:49.000Z","size":113,"stargazers_count":20,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-12T07:52:08.637Z","etag":null,"topics":["object","observer","proxy","rxjs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexandercerutti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-02-24T21:58:08.000Z","updated_at":"2024-01-20T19:25:47.000Z","dependencies_parsed_at":"2023-03-13T16:36:46.371Z","dependency_job_id":null,"html_url":"https://github.com/alexandercerutti/roxe","commit_stats":{"total_commits":139,"total_committers":1,"mean_commits":139.0,"dds":0.0,"last_synced_commit":"18176930377b5af0a51210aa7bebfd1c129aceec"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandercerutti%2Froxe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandercerutti%2Froxe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandercerutti%2Froxe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexandercerutti%2Froxe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexandercerutti","download_url":"https://codeload.github.com/alexandercerutti/roxe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248788925,"owners_count":21161727,"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":["object","observer","proxy","rxjs"],"created_at":"2024-11-27T13:35:59.396Z","updated_at":"2025-04-13T22:11:04.830Z","avatar_url":"https://github.com/alexandercerutti.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Roxe\r\n\r\n![](https://img.shields.io/npm/v/roxe.svg?label=roxe)\r\n\r\nRoxe is a lightweight utility created to observe changes to complex objects structures. This is mainly designed for event-driven applications, usually with multiple files, that need to execute some side effects, when a central object has its properties changed.\r\n\r\nIt allows to listen for changes of a generic Object, nested objects, Arrays, arrays' values, Objects in Arrays, Objects in Objects in Array, and... yeah, you got it.\r\n\r\nIt merges the powerfulness of RxJS and ES Proxies to let developers subscribe to changes to every property, also nested ones and arrays, in the observed object.\r\n\r\n___\r\n\r\n## Looking for previous version?\r\n\r\nCheck [v1.0.9 branch](https://github.com/alexandercerutti/roxe/tree/1.0.9). That branch is kept for reference only.\r\n\r\n\u003cbr\u003e\r\n\r\n___\r\n\r\n\r\n## Install\r\n\r\n```sh\r\n$ npm install --save roxe\r\n```\r\n\r\n\u003cbr\u003e\r\n\u003chr\u003e\r\n\r\n## Usage example\r\n\r\nIn this usage example, I'm supposing you are using Typescript to show you the real force of this package.\r\n\r\nInstantiate a new class for every root object that contains a property you want to observe and pass the descriptive _typescript interface_ to `ObservableObject`'s generic parameter. This will let you to access to object's properties, like\r\n`nestedObjects.foo`, without ignoring the errors.\r\n\r\nObserve a specific chain-object (a dotted-separated string, as below) and then... subscribe!!! 🎉🎉🎉💁‍♂️\r\n\r\n```typescript\r\nimport { ObservableObject } from \"roxe\";\r\n\r\ninterface CustomObject {\r\n\t// here lays you object definition\r\n\tfoo: number;\r\n\tbar?: {\r\n\t\tbaz: number;\r\n\t\tbeer: {\r\n\t\t\tbeersAmount: number\r\n\t\t};\r\n\t\tvalueSet?: Array\u003c{\r\n\t\t\tvalue: number;\r\n\t\t\t[key: string]: any;\r\n\t\t}\u003e;\r\n\t}\r\n}\r\n\r\nconst nestedObjects = new ObservableObject\u003cCustomObject\u003e({\r\n\tfoo: 5,\r\n\tbar: {\r\n\t\tbaz: 7,\r\n\t\tbeer: {\r\n\t\t\tbeersAmount: 55\r\n\t\t},\r\n\t\tvalueSet: [\r\n\t\t\t{ value: 3 },\r\n\t\t\t{ value: 4 },\r\n\t\t\t{ value: 5 },\r\n\t\t],\r\n\t}\r\n});\r\n\r\n// Subscribe to the object changes.\r\n\r\nconst firstPropertyObserver = nestedObjects.observe(\"foo\");\r\nconst subscription = firstPropertyObserver.subscribe({\r\n\tnext: (newValue =\u003e {\r\n\t\tconsole.log(\"'foo' property changed!\");\r\n\t});\r\n});\r\n\r\nconst sub1 = nestedObjects.observe(\"bar.beer.beersAmount\")\r\n\t.subscribe( ... );\r\n\r\nconst valueSetAmountSubscription = nestedObjects.observe(\"bar.valueSet.length\").subscribe({\r\n\tnext: (length: number) =\u003e {\r\n\t\tconsole.log(`valueSet amount just changed to ${length}`);\r\n\t}\r\n});\r\n\r\nconst valueSetElementValueSubscription = nestedObjects.observe(\"bar.valueSet.3.value\").subscribe({\r\n\tnext: (newValue: number) =\u003e {\r\n\t\tconsole.log(`bar.valueSet's fourth element has been just added or has its 'value' field changed to ${newValue}`);\r\n\t}\r\n})\r\n\r\n// Else where, edit this object.\r\n\r\nnestedObjects.foo = 1;\r\n\r\n/**\r\n * Notifications:\r\n * \t`foo: 1`\r\n */\r\n\r\n// Now drinking some beers and probably something else\r\nnestedObjects.bar = {\r\n\tbaz: 3,\r\n\tbeer: {\r\n\t\tbeersAmount: 4,\r\n\t}\r\n};\r\n\r\n/**\r\n * Notifications Result:\r\n *\tbar: { baz: 3, beer: { beersAmount: 4 }};\r\n *\tbar.baz: 3\r\n *\tbar.beer: { beersAmount: 4 }\r\n *\tbar.beer.beersAmount: 4\r\n *\tbar.valueSet: undefined\r\n *\tbar.valueSet[0].value: undefined\r\n *\tbar.valueSet[0]: undefined\r\n *\tbar.valueSet[1].value: undefined\r\n *\tbar.valueSet[1]: undefined\r\n *\tbar.valueSet[2].value: undefined\r\n *\tbar.valueSet[2]: undefined\r\n *\tbar.valueSet.length: undefined\r\n *\r\n * Holy shit, now I'm drunk 🥴😵\r\n */\r\n\r\ndelete nestedObjects.bar;\r\n\r\n/**\r\n * Notification Result:\r\n * \tbar: undefined\r\n * \tbar.baz: undefined,\r\n * \tbar.beer: undefined,\r\n * \tbar.beer.beersAmount: undefined\r\n */\r\n```\r\n\r\n\u003cbr\u003e\r\n\u003cbr\u003e\r\n\r\n___\r\n\r\n## API Documentation\r\n___\r\n\r\n\r\n**constructor()**\r\n\r\n```typescript\r\nnew ObservableObject\u003cT\u003e(from?: T, optHandlers?: ProxyHandler\u003cany\u003e): T \u0026 ObservableObject\u003cT\u003e;\r\nnew ObservableObject(from, optHandlers);\r\n```\r\n\r\n**Arguments**:\r\n\r\n| Key | Type | Description | Optional | Default |\r\n|-----|:----:|-------------|:--------:|:-------:|\r\n| from | T    | The object you want to observe | `true` | `{}` |\r\n| optHandlers | ProxyHandler\u003cany\u003e | Other handlers through which the object changes developers may them want to pass through. A set handler will be executed before the notification will happen, within the current one. See more about custom traps at [Defining custom traps](#customtraps) below.  | `true` | `{}` |\r\n\r\n\u003cbr\u003e\r\n\r\n___\r\n\r\n\u003cbr\u003e\r\n\r\n**.observe()**\r\n\r\n```typescript\r\nobservableObject.observe\u003cA = any\u003e(prop: string): Observable\u003cA\u003e;\r\n```\r\n\r\n**Description**:\r\n\r\nUse this method to create an Observable to which you can subscribe to.\r\nThis methods accepts a generic type `A` that will be passed to the creation of the Subject.\r\n\r\n\u003cbr\u003e\r\n\r\n**Returns**:\r\n\r\n`Observable\u003cA\u003e`\r\n\r\n\u003cbr\u003e\r\n\r\n**Arguments**:\r\n\r\n| Key  | Type | Description |\r\n|------|:----:|-------------|\r\n| prop |string| The key-path to identify the property, or object, to observe. |\r\n\r\n\u003cbr\u003e\r\n\r\n___\r\n\u003cbr\u003e\r\n\r\n**.snapshot()**\r\n\r\n```typescript\r\nobservableObject.snapshot(path?: string): any;\r\n```\r\n\r\n**Description**:\r\n\r\nReturns a clean (no proxies or internal props) object structure or value of a nested property.\r\nReturns `undefined` if the specified observed object doesn't own a middle or end key of the specified path.\r\n\r\n\u003cbr\u003e\r\n\r\n**Arguments**:\r\n\r\n| Key  | Type | Description |\r\n|------|:----:|-------------|\r\n| path |string| The key-path to identify the property, or object, to snap. |\r\n\r\n**Caveats**:\r\n\r\nAvoid to snap(shot) your fingers or Thanos will be happy.\r\n\r\n\u003cbr\u003e\r\n\u003cbr\u003e\r\n\r\n___\r\n\r\n\u003ca name=\"customtraps\"\u003e\u003c/a\u003e\r\n\r\n## Defining custom traps\r\n\r\nThis package has been built with in mind the possibility to be extendable as much as possible.\r\nIn fact, observable object's constructor allows custom proxy handlers to be passed. Traps will be applied to all the nested objects.\r\n\r\nAs v2.0, *set* and *deleteProperty*  are the only two handlers that are \"protected\": they cannot be fully overridden, but external ones will influence the final result of internal ones.\r\n\r\nIn fact, if a custom `set` returns **false**, properties won't be changed and notifications won't be fired.\r\n\r\nIf a custom `deleteProperty` returns **false**, notifications won't be executed and the property won't be removed from the object.\r\n\r\n\u003e Be sure to not overload the traps, or your application performance may have affected.\r\n\r\n___\r\n## Testing\r\n\r\nSome tests based on Jasmine, are available to be executed to show how this package works.\r\n\r\n```sh\r\n$ npm install -D\r\n$ npm test\r\n```\r\n\r\n___\r\n## Caveats\r\n\r\nThis project compiles to a `CommonJS` module as the original project this comes from (see below), used Webpack, which does not digest `UMD` modules (using some loaders was breaking the debugging process for some reasons). If you'll need to get the UMD version, an npm script to achieve this has been made available. Follow the script below:\r\n\r\n```sh\r\n# !! change to the folder you want to clone into, first !!\r\ngit clone https://github.com/alexandercerutti/roxe.git;\r\ncd roxe;\r\nnpm run build:umd;\r\n```\r\n\r\nThis will output a new `umd/` folder with the generated files. To conclude the process, copy the whole content of `umd/` to a new `roxe` folder in your project and create a link or commit it this package on your repo to avoid losing it.\r\n\r\nAnother caveat is about the weight of this package. To be powerful, this package brings with it, as dependency, the whole RxJS library.\r\nSo, even if this library weights few KBs, Rx.JS contributes a lot to the general weight. This package might be considered as an extension for RxJS, but it won't have RxJS as `peerDependency` to allow everyone to use it in every context.\r\n\r\n___\r\n## Credits ⭐\r\n\r\nThis small package is a fork of a package, created by me, while working at [IdeaSolutions S.r.l.](http://www.ideasolutions.it/), an Italian company based in Naples, Italy. A great company to work for.\r\n\r\n\u003cbr\u003e\r\n\u003cbr\u003e\r\n\r\n___\r\n\r\nMade with ❤ in Italy.\r\nEvery contribution is welcome.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandercerutti%2Froxe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexandercerutti%2Froxe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandercerutti%2Froxe/lists"}