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();
});
});