Skip to content

Commit

Permalink
feat: api and direction updated
Browse files Browse the repository at this point in the history
  • Loading branch information
betula committed Dec 23, 2023
1 parent 451433b commit be08004
Show file tree
Hide file tree
Showing 10 changed files with 463 additions and 544 deletions.
405 changes: 405 additions & 0 deletions DOCUMENTATION.md

Large diffs are not rendered by default.

379 changes: 5 additions & 374 deletions README.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/configure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { configure } from 'mobx';

configure({
enforceActions: "never",
});
27 changes: 0 additions & 27 deletions src/core.ts

This file was deleted.

38 changes: 0 additions & 38 deletions src/decorator.ts

This file was deleted.

4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export {
type Signal,
type ReadonlySignal,
signal,
wrap
} from './signal';

export { observer as component } from 'mobx-react-lite';
export { untracked, transaction } from 'mobx';

export { signal, computed } from './core';
import './configure';

export { un } from 'unsubscriber';

Expand Down
49 changes: 31 additions & 18 deletions src/signal.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,49 @@
import { observable, untracked, computed as mobxComputed } from 'mobx';
import { observable, untracked, computed as mobxComputed, comparer } from 'mobx';
import { reaction, sync } from './reaction';

export interface ReadonlySignal<T> {
get value(): T;
get(): T;
subscribe(listener: (current: T, prev: T) =>void): () => void;
sync(listener: (current: T, prev: T | undefined) =>void): () => void;
}

export interface Signal<T> extends ReadonlySignal<T> {
(value: T): T,
update(fn: (value: T) => T);
}

export function signalFactory<T>(value: T, equals?: (a: T, b: T) => boolean): Signal<T> {
const box = observable.box(value, { equals });
const set = box.set.bind(box);
export function signal<T>(value: T): Signal<T> {
const box = observable.box(value, { equals: comparer.default });
const get = box.get.bind(box);
const set = box.set.bind(box);
return make(get, set);
}

Object.defineProperty(set, 'value', { get });
set.get = get;
set.update = (fn: (value: T) => T) => set(fn(untracked(set.get)));

return set;
export function wrap<T>(readfn: () => T): ReadonlySignal<T>;
export function wrap<T>(readfn: () => T, writefn: (value: T) => void): Signal<T>;
export function wrap<T>(readfn: () => T, writefn?: (value: T) => void): (ReadonlySignal<T> | Signal<T>) {
const box = mobxComputed(readfn, { equals: comparer.shallow });
const get = box.get.bind(box);
return make(get, writefn);
}

export function computedFactory<T>(fn: () => T, equals?: (a: T, b: T) => boolean): ReadonlySignal<T> {
const box = mobxComputed(fn, { equals });
const get = box.get.bind(box);
const h = {
function make<T>(readfn: () => T): ReadonlySignal<T>;
function make<T>(readfn: () => T, writefn: (value: T) => void): Signal<T>;
function make<T>(readfn: () => T, writefn?: (value: T) => void): (ReadonlySignal<T> | Signal<T>) {
const get = readfn;
const h = writefn || {};
Object.defineProperty(h, 'value', { get })
Object.assign(h, {
get,
get value() {
return get();
}
};
subscribe: (listener) => reaction(get, listener),
sync: (listener) => sync(get, listener)
});
if (writefn) {
Object.assign(h, {
update: (fn: (value: T) => T) => writefn(fn(untracked(get))),
})
}

return h;
return h as any;
}
6 changes: 3 additions & 3 deletions test/hook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ it('hook works', () => {
const destroy_spy = jest.fn();

class A {
@signal a: number;
a = signal<number>(0);
b = 0;
constructor() {
unsubs = scope();
create_spy();
un(destroy_spy);

autorun(() => {
this.b = (this.a || 0) + 10;
this.b = (this.a.value || 0) + 10;
});
}
}
Expand All @@ -45,7 +45,7 @@ it('hook works', () => {
const inst = useA();

expect(inst.b).toBe(10);
inst.a = 10;
inst.a(10);
expect(inst.b).toBe(20);

expect(create_spy).toBeCalled();
Expand Down
18 changes: 8 additions & 10 deletions test/service.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { service, un, signal, computed } from "../src";
import { service, un, signal, wrap } from "../src";

it('service works', () => {
const create_spy = jest.fn();
const destroy_spy = jest.fn();

class A {
@signal m = 1;
@computed get c() {
return this.m + 1;
}
m = signal(1);
c = wrap(() => this.m.value + 1);

constructor() {
create_spy();
Expand All @@ -26,9 +24,9 @@ it('service works', () => {
expect(s.a()).toBe(1);
expect(create_spy).toBeCalled();

expect(s.c).toBe(2);
s.m += 1;
expect(s.c).toBe(3);
expect(s.c.value).toBe(2);
s.m.update(v => v + 1);
expect(s.c.value).toBe(3);

expect(destroy_spy).not.toBeCalled();
service.destroy(s);
Expand All @@ -37,8 +35,8 @@ it('service works', () => {
create_spy.mockReset();
destroy_spy.mockReset();

s.m = 2;
expect(s.c).toBe(3);
s.m(2);
expect(s.c.value).toBe(3);
expect(create_spy).toBeCalled();

expect(destroy_spy).not.toBeCalled();
Expand Down
76 changes: 3 additions & 73 deletions test/works.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { computed, reaction, signal, transaction, sync, when } from "../src";
import { wrap, reaction, signal, transaction, sync, when } from "../src";

it('signal works', () => {
const spy = jest.fn();
Expand All @@ -13,11 +13,11 @@ it('signal works', () => {
expect(spy).toBeCalledTimes(3);
});

it('computed works', () => {
it('wrap works', () => {
const spy = jest.fn();
const a = signal(0);
const b = signal(0);
const c = computed(() => a.value + b.value);
const c = wrap(() => a.value + b.value);
sync(() => c.value, (v) => spy(v));
a(1);

Expand All @@ -37,76 +37,6 @@ it('computed works', () => {
expect(spy).toBeCalledTimes(3);
});

it('signal decorator works', () => {
const spy = jest.fn();
class S {
@signal a = 10;
}
const s = new S();
sync(() => s.a, (v) => spy(v));
s.a = 1;
s.a += 1;

expect(spy).nthCalledWith(1, 10);
expect(spy).nthCalledWith(2, 1);
expect(spy).nthCalledWith(3, 2);
expect(spy).toBeCalledTimes(3);
});

it('computed decorator works', () => {
const spy = jest.fn();
class S {
@signal a = 0;
@signal b = 0;
@computed get c() {
return this.a + this.b;
}
}
const s = new S();

sync(() => s.c, (v) => spy(v));
s.a = 1;

transaction(() => {
s.a += 1;
s.b += 2;
});

expect(spy).nthCalledWith(1, 0);
expect(spy).nthCalledWith(2, 1);
expect(spy).nthCalledWith(3, 4);

transaction(() => {
s.a ++;
s.b --;
});
expect(spy).toBeCalledTimes(3);
});

it('decorator works with class extends', () => {
const spy = jest.fn();
class A {
@signal a = 10;
}
class B extends A {
@signal b = 11;
}
class C extends B {
@computed get c() {
return this.a + this.b;
};
}
const s = new C();
sync(() => s.c, (v) => spy(v));
s.a = 1;
s.b += 1;

expect(spy).nthCalledWith(1, 21);
expect(spy).nthCalledWith(2, 12);
expect(spy).nthCalledWith(3, 13);
expect(spy).toBeCalledTimes(3);
});

it('reaction works', () => {
const spy = jest.fn();
const s = signal(0);
Expand Down

0 comments on commit be08004

Please sign in to comment.