Skip to content

Commit

Permalink
add subscribe/notify feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
yesil committed Dec 16, 2024
1 parent aefcbd2 commit 2eb2780
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 30 deletions.
80 changes: 67 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br>
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 <br>
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.
28 changes: 28 additions & 0 deletions dist/picosm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -134,6 +159,7 @@ function track(target, source) {
});
target.__resetComputedProperties();
target.__notifyObservers();
return disposer;
}

// src/LitObserver.js
Expand Down Expand Up @@ -182,7 +208,9 @@ function litObserver(constructor, properties) {
export {
litObserver,
makeObservable,
notify,
observe,
reaction,
subscribe,
track
};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
28 changes: 28 additions & 0 deletions src/makeObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
});
}
1 change: 1 addition & 0 deletions src/track.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export function track(target, source) {
});
target.__resetComputedProperties();
target.__notifyObservers();
return disposer;
}
23 changes: 11 additions & 12 deletions test/TestStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/track.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});

0 comments on commit 2eb2780

Please sign in to comment.