{"id":13623114,"url":"https://github.com/lukeed/sublet","last_synced_at":"2025-10-09T18:24:27.371Z","repository":{"id":57373931,"uuid":"186519742","full_name":"lukeed/sublet","owner":"lukeed","description":"Reactive leases for data subscriptions","archived":false,"fork":false,"pushed_at":"2019-06-27T22:15:00.000Z","size":23,"stargazers_count":140,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-08T05:51:19.830Z","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/lukeed.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}},"created_at":"2019-05-14T01:13:08.000Z","updated_at":"2024-08-07T00:29:19.000Z","dependencies_parsed_at":"2022-09-17T16:51:58.705Z","dependency_job_id":null,"html_url":"https://github.com/lukeed/sublet","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeed%2Fsublet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeed%2Fsublet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeed%2Fsublet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukeed%2Fsublet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukeed","download_url":"https://codeload.github.com/lukeed/sublet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223668746,"owners_count":17182974,"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-08-01T21:01:28.215Z","updated_at":"2025-10-09T18:24:22.328Z","avatar_url":"https://github.com/lukeed.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# sublet [![Build Status](https://badgen.net/travis/lukeed/sublet)](https://travis-ci.org/lukeed/sublet)\n\n\u003e Reactive leases for data subscriptions\n\nWith `sublet` you can directly attach functions to the data they care about.\n\nYou marry a function with its relevant data. They become bound to one another, a \"reactive\" pair.\u003cbr\u003e\nWhen the data changes, the function automatically gets triggered, receiving the latest snapshot of that data.\n\nTheir \"reactive marriage\" is also exposed to the outside world in the form of a proxied `data` object.\u003cbr\u003e\nThis allows external reads _and writes_ into `data` object. Because the function is bound, it will still always be triggered, regardless of who/what caused those updates!\n\nWhenever the binding updates, the original `data` source receives the updates, too.\u003cbr\u003e\nThis means that when new reactive pairs source their data from other bindings (or pieces of them), any updates to the \"child\" binding propagate to the \"parent\" binding. In turn, both of their partner functions are triggered.\n\nSo, `sublet` _sort of_ behaves like a store, but its dispatchers are normal variable assignments!\u003cbr\u003e\nInstead, `sublet` allows you to get back to JavaScript's basic building blocks while keeping your sanity.\n\n## Modes\n\nThere are two \"versions\" of `sublet`, accommodating different [browser support](#browser-support) targets:\n\n#### \"proxy\"\n\u003e **Size (gzip):** 194 bytes\u003cbr\u003e\n\u003e **Availability:** [UMD](https://unpkg.com/sublet), [CommonJS](https://unpkg.com/sublet/dist/index.js), [ES Module](https://unpkg.com/sublet?module)\u003cbr\u003e\n\u003e **Requires:** [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Browser_compatibility)\n\nThe more modern build of `sublet` as it relies on the `Proxy` class.\u003cbr\u003e\nBecause of this, the \"modern\" version of `sublet` can work with Objects and Arrays, neither of which require the `input` to have a predefined shape. This means that your Objects can receive new keys at any point, or your Arrays can mutate in any which way, and your `callback` subscriber will always be made aware of those changes.\n\n#### \"legacy\"\n\u003e **Size (gzip):** 263 bytes\u003cbr\u003e\n\u003e **Availability:** [UMD](https://unpkg.com/sublet/legacy), [ES Module](https://unpkg.com/sublet/legacy/index.mjs)\u003cbr\u003e\n\u003e **Requires:** [`Array.isArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray#Browser_compatibility), [`Object.defineProperty`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Browser_compatibility)\n\nThe \"legacy\" version is bound by `Object.defineProperty`'s limitations:\n\n1) the `input` must be an Object (no Arrays);\n2) the `input` Object must have its keys defined ahead of time, before `sublet` instantiation.\n\nUnlike the `Proxy`-based solution, any new, dynamically added keys are _unknown_ and _uninitialized_, which means they cannot and will never be able to trigger the `callback` when they show up or change.\n\n\n## Install\n\n```sh\n$ npm install --save sublet\n```\n\nAlso available on [unpkg.com](https://unpkg.com):\n\n```html\n\u003c!-- Mode: \"proxy\" --\u003e\n\u003cscript src=\"https://unpkg.com/sublet\"\u003e\u003c/script\u003e\n\n\u003c!-- Mode: \"legacy\" --\u003e\n\u003cscript src=\"https://unpkg.com/sublet/legacy\"\u003e\u003c/script\u003e\n```\n\n## Usage\n\n```js\n// Mode: \"proxy\"\nimport sublet from 'sublet';\n// Mode: \"legacy\"\nimport sublet from 'sublet/legacy';\n\nconst user = {}; // \u003c~ \"proxy\" can be lazy\nconst user = { firstName:null, lastName:null }; // \u003c~ \"legacy\" is explicit\n\nconst view = sublet(user, state =\u003e {\n  if (state.firstName) {\n    console.log(`Hello, ${state.firstName} ${state.lastName}`);\n  } else {\n    console.log('Howdy stranger~!');\n  }\n});\n//=\u003e \"Howdy stranger~!\"\n\nview.firstName = 'Nicolas';\nview.lastName = 'Cage';\n//=\u003e \"Hello, Nicolas Cage\"\n```\n\n\n## API\n\n### sublet(input, callback)\n\nReturns: `T\u003cinput\u003e`\n\nA wrapped (aka, proxied) form of your `input` is returned.\u003cbr\u003e\nThis is the reactive interface and should be used to update state.\u003cbr\u003e\nThis interface is also passed to `callback` as its only argument.\n\nAny updates through this `T` interface will propagate new value(s) to the original `input`, too.\u003cbr\u003e\nSimilarly, all read operations pull from the current `input` object. This means that any updates to `input` directly (outside of the `T` interface) **will not** trigger the `callback` subscription; however, those values _will_ appear when read through `T` and/or in the next `callback` invocation.\n\n\u003e **Note:** In \"proxy\" mode, the `T` is a `Proxy` instance and in \"legacy\" mode, it is an `Object`.\u003cbr\u003eHowever, these are functionally equivalent since a `Proxy` can't be detected anwyay.\n\n#### input\nType: `Object`\n\nThe original state data to observe.\n\n\u003e ***Important:*** Modes operate differently!\n\u003e\n\u003e ***Mode: \"proxy\"***\n\u003e * `Array` types are permitted\n\u003e * When an `Object`, the keys _do not_ need to be declared upfront\n\u003e\n\u003e ***Mode: \"legacy\"***\n\u003e\n\u003e * `Array` types are **not** permitted\u003cbr\u003e\n\u003e     _The original `input` is immediately returned, without `callback` attachment._\n\u003e * The `Object` must have predefined keys!\u003cbr\u003e\n\u003e     _Reactivity can **only** be established for known keys._\u003cbr\u003e\n\u003e     _You must define your default or empty state._\u003cbr\u003e\n\n#### callback\nType: `Function`\n\nThe callback to run whenever the paired reactive data updates.\u003cbr\u003e\nThis function receives the reactive data (`T\u003cinput\u003e`) as its only argument.\n\nThe callback will run immediately when setting up a `sublet` instance.\u003cbr\u003e\nThis means that your callback should be capable of handling \"setup\" vs \"update\" usage. Alternatively, you can separate those actions and attach the \"update\" function as your `sublet` subscriber.\n\nAfter initializing, the `callback` will only be _queued_ to run if an updated value was not _strictly equal_ to its previous value.\u003cbr\u003e\nSimilarly, any updates to the `T\u003cinput\u003e` argument _will_ enqueue a new update cycle.\n\nFinally, the `callback` is debounced. This means that multiple `T\u003cinput\u003e` updates will re-run the `callback` once.\n\n```js\nimport sublet from 'sublet';\n\nlet num = 0;\nconst view = sublet({}, () =\u003e console.log(`Render #${++num}`));\n//=\u003e Render #1\n\nview.foo = 123;\n// Render #2\n\nawait sleep(10); //~\u003e 10ms later\n\nview.foo = 123;\n// (no render, value identical)\n\nawait sleep(10); //~\u003e 10ms later\n\nview.foo = 1;\nview.bar = 2;\nview.baz = 3;\n//=\u003e Render #3\n```\n\n\n## Browser Support\n\nThe \"legacy\" version works just about everywhere.\u003cbr\u003e\nThe \"proxy\" version works anywhere that was considered \"modern\" in the last 4 years.\n\n|  | Chrome | Safari | Firefox | Edge | Opera | IE | Node.js\n| - | - | - | - | - | - | - | - |\n| \"proxy\"  | 49 | 10 | 18 | 12 | 36 | :x: | 6.0.0\n| \"legacy\"  | 5 | 5.1 | 4 | 12 | 11.6 | 9 | :white_check_mark:\n\n\n## Prior Art\n\nThere are 101 observable/reactive libraries and patterns out there – it would be impossible to name them all.\n\nHowever, the [reactivity of Svelte 3.x](https://svelte.dev/examples#reactive-assignments), specifically, inspired me.\n\n## License\n\nMIT © [Luke Edwards](https://lukeed.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukeed%2Fsublet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukeed%2Fsublet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukeed%2Fsublet/lists"}