From 2eb2780db3fb317d8ab978d80ada95b6eb064b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilyas=20T=C3=BCrkben?= Date: Mon, 16 Dec 2024 14:27:28 +0100 Subject: [PATCH] add subscribe/notify feature. --- README.md | 80 ++++++++++++++++++++++++++++++++++++------- dist/picosm.js | 28 +++++++++++++++ package-lock.json | 4 +-- package.json | 2 +- src/makeObservable.js | 28 +++++++++++++++ src/track.js | 1 + test/TestStore.js | 23 ++++++------- test/track.test.js | 4 +-- 8 files changed, 140 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index f886ea5..4c38463 100644 --- a/README.md +++ b/README.md @@ -4,36 +4,90 @@ Live demo: https://yesil.github.io/picosm ## Introduction -This is an experimental lightweight, zero dependency state manager that attempts to replicate core MobX features without using Proxy objects. +Pico State Manager is an experimental, lightweight, zero-dependency state manager that replicates core MobX features without using Proxy objects. It also provides a helper function to make Lit components observers and updates them on changes. -Currently, the non minified bundle size is `4938 bytes` and is around `~1.2kb` gzipped. +Currently, the non-minified bundle size is `5992 bytes` and around `1536 bytes` gzipped. -## Partially Supported Features +You can also import only the specific modules you need from the source to further reduce the size. -- `makeObservable`: generates a new class whose instances are observable -- `observe`: callback when the instance of an observable class changes -- `reaction`: react to specific changes -- `computed`: cache computed values +## Partially Supported Features -Also, an opinionated approach for tracking nested dependencies with `track` functions is added.
-If an observable wants to get notified by another one, these functions can be used. +- `makeObservable`: Generates a new class whose instances are observable. +- `observe`: Callback when the instance of an observable class changes. +- `reaction`: React to specific changes. +- `track`: Tracks other observables and notifies own observers on change. +- `subscribe`: Subscribe to changes in an observable. +- `notify`: Notify all subscribers of changes. +- `computed`: Cache computed values. ## How to Use -Add the picosm dependency +Add the picosm dependency: ```bash -npm add https://github.com/yesil/picosm/releases/download/v1.0.2/picosm-1.0.2.tgz +npm add https://github.com/yesil/picosm/releases/download/v1.0.3/picosm-1.0.3.tgz ``` See the unit test: https://github.com/yesil/picosm/blob/main/test/LitObserver.test.js
See the demo app source code: https://github.com/yesil/picosm/tree/main/app for a more comprehensive example. +## API Documentation + +### `makeObservable` + +Generates a new class whose instances are observable. + +### `observe` + +Creates an observer for a given observable. + +Returns a `disposer` function. + +### `reaction` + +React to specific changes. + +Returns a `disposer` function. + +### `track` + +Tracks other observables and notifies own observers on change. + +Returns a `disposer` function. + +### `subscribe` + +An observable becomes automatically a subscription queue, where one can register several subscriptions for receiving arbitrary messages. + +Returns a `disposer` function. + +#### Example + +```javascript +const disposer = subscribe(observableInstance, (message) => { + console.log('Message received', message); +}); +``` + +### `notify` + +Notify all subscribers of changes. + +#### Example + +```javascript +notify(observableInstance, message); +``` + +### `computed` + +Cache computed values. + ## Contributing Please feel free to: -Open a PR to contribute. -Create an issue to request a feature. +- Open a PR to contribute. +- Create an issue to request a feature. diff --git a/dist/picosm.js b/dist/picosm.js index 4827c5e..7f6c183 100644 --- a/dist/picosm.js +++ b/dist/picosm.js @@ -76,6 +76,23 @@ function makeObservable(constructor2, actions = [], computeds = []) { this.__observers.delete(callback); }; } + __subscribe(onMessageCallback) { + if (!this.__subscribers) { + Object.defineProperties( + this, + { + __subscribers: { value: /* @__PURE__ */ new Set() } + }, + { + __subscribers: { enumerable: false, writable: false } + } + ); + } + this.__subscribers.add(onMessageCallback); + return () => { + this.__subscribers.delete(onMessageCallback); + }; + } } actions.forEach((methodName) => { instrumentAction(SuperClass, methodName); @@ -100,6 +117,14 @@ function observe(target, callback, timeout) { return target.__observe(callback); } } +function subscribe(target, onMessageCallback) { + return target.__subscribe(onMessageCallback); +} +function notify(target, message) { + target.__subscribers?.forEach((listener) => { + listener(message); + }); +} // src/reaction.js function reaction(target, callback, execute, timeout) { @@ -134,6 +159,7 @@ function track(target, source) { }); target.__resetComputedProperties(); target.__notifyObservers(); + return disposer; } // src/LitObserver.js @@ -182,7 +208,9 @@ function litObserver(constructor, properties) { export { litObserver, makeObservable, + notify, observe, reaction, + subscribe, track }; diff --git a/package-lock.json b/package-lock.json index 0ea6009..5bf9c11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "picosm", - "version": "1.0.1", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "picosm", - "version": "1.0.1", + "version": "1.0.3", "license": "ISC", "dependencies": { "@spectrum-web-components/theme": "^1.0.1" diff --git a/package.json b/package.json index e2c9b66..d21476f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "picosm", - "version": "1.0.2", + "version": "1.0.3", "main": "dist/picosm.js", "scripts": { "test": "web-test-runner test/**/*.test.js --node-resolve", diff --git a/src/makeObservable.js b/src/makeObservable.js index cf698d0..ecb68bb 100644 --- a/src/makeObservable.js +++ b/src/makeObservable.js @@ -84,6 +84,24 @@ export function makeObservable(constructor, actions = [], computeds = []) { this.__observers.delete(callback); }; } + + __subscribe(onMessageCallback) { + if (!this.__subscribers) { + Object.defineProperties( + this, + { + __subscribers: { value: new Set() }, + }, + { + __subscribers: { enumerable: false, writable: false }, + }, + ); + } + this.__subscribers.add(onMessageCallback); + return () => { + this.__subscribers.delete(onMessageCallback); + }; + } } actions.forEach((methodName) => { @@ -111,3 +129,13 @@ export function observe(target, callback, timeout) { return target.__observe(callback); } } + +export function subscribe(target, onMessageCallback) { + return target.__subscribe(onMessageCallback); +} + +export function notify(target, message) { + target.__subscribers?.forEach((listener) => { + listener(message); + }); +} diff --git a/src/track.js b/src/track.js index 9fb1a9a..71ec30a 100644 --- a/src/track.js +++ b/src/track.js @@ -5,4 +5,5 @@ export function track(target, source) { }); target.__resetComputedProperties(); target.__notifyObservers(); + return disposer; } diff --git a/test/TestStore.js b/test/TestStore.js index cd1860d..7b8909a 100644 --- a/test/TestStore.js +++ b/test/TestStore.js @@ -2,38 +2,37 @@ import { track } from '../src/track.js'; export default class TestStore { #disposer; + #connection; + #counter = 0; + constructor() { this.checked = false; - this._counter = 0; - this._test = undefined; } toggleCheck() { this.checked = !this.checked; - this._counter++; + this.#counter++; } async toggleAsyncCheck() { await new Promise((resolve) => setTimeout(() => { this.checked = !this.checked; - this._counter++; + this.#counter++; resolve(true); }, 100), ); } get counter() { - return this._counter + (this._test ? this._test.counter : 0); + return this.#counter + (this.#connection ? this.#connection.counter : 0); } - set test(test) { - if (this._test) { - this.#disposer?.(); - } - this._test = test; - if (test) { - this.#disposer = track(this, test); + connect(store) { + this.#disposer?.(); + this.#connection = store; + if (store) { + this.#disposer = track(this, store); } } diff --git a/test/track.test.js b/test/track.test.js index 1292c11..df9c562 100644 --- a/test/track.test.js +++ b/test/track.test.js @@ -19,12 +19,12 @@ describe('Pico State Manager', function () { expect(test1.counter).to.equal(1); expect(test2.counter).to.equal(1); - test1.test = test2; + test1.connect(test2); test2.toggleCheck(); expect(test1.counter).to.equal(3); - test1.test = undefined; + test1.connect(); test2.toggleCheck(); }); });