From a7ca62ebb58c3a9ac31e2d1753cfd21740489813 Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 14 May 2021 14:50:50 +0700 Subject: [PATCH 001/100] feat: next _value first draft impl --- src/index.ts | 256 +++++++++++++++++++++++++++++++++++++++++++++ tests/_api.test.ts | 127 ++++++++++++++++++++++ tsconfig.json | 2 +- 3 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 tests/_api.test.ts diff --git a/src/index.ts b/src/index.ts index e756617f..64f6aff4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,6 +80,262 @@ let scope_context: any; let stoppable_context: any; const def_prop = Object.defineProperty; + + + + + + + + + + + + + + + +const obj_equals = Object.is; +const obj_def_prop = Object.defineProperty; +const obj_create = Object.create; + +const key_proto = "__proto__"; + +const obj_def_prop_value = (obj, key, value) => ( + obj_def_prop(obj, key, { value }), value +); + +const proto_def_prop_trait = (obj, key, trait) => + obj_def_prop(obj, key, { + get() { + return obj_def_prop_value(this, key, trait.bind(void 0, this)); + } + }); + +const proto_def_prop_trait_with_ns = (obj, key, trait, ns) => + obj_def_prop(obj, key, { + get() { + const binded = trait.bind(void 0, this); + binded[key_proto] = ns; + return obj_def_prop_value(this, key, binded); + } + }); + +const proto_def_prop_factory = (obj, key, factory) => + obj_def_prop(obj, key, { + get() { + return obj_def_prop_value(this, key, factory(this)); + } + }); + +const proto_base_pure_fn = function () {}; + +const key_get = "get"; +const key_set = "set"; +const key_promise = "promise"; +const key_promise_internal = Symbol(); +const key_reset = "reset"; +const key_initial = Symbol(); +const key_dirty = "dirty"; +const key_sync = "sync"; + +const proto_def_prop_promise = (obj) => { + return obj_def_prop(obj, key_promise, { + get() { + const ctx = this; + if (!ctx[key_promise_internal]) { + ctx[key_promise_internal] = new Promise((resolve) => + expr(ctx[key_get], () => { + ctx[key_promise_internal] = void 0; + resolve(ctx[key_get]()); + })[0]() + ); + } + return ctx[key_promise_internal]; + } + }); +}; + +const proto_def_prop_promise_for_trigger = (obj) => { + return obj_def_prop(obj, key_promise, { + get() { + const ctx = this; + if (!ctx[key_promise_internal]) { + ctx[key_promise_internal] = new Promise((resolve) => + expr(ctx[key_get], () => resolve(ctx[key_get]()))[0]() + ); + } + return ctx[key_promise_internal]; + } + }); +}; + +const prop_factory_reset_required_promise_and_initial = (ctx) => { + const b = box([]); + const ret = () => { + if (!obj_equals(ctx[key_initial], ctx[key_get]())) { + ctx[key_promise_internal] = 0; + ctx[key_set](ctx[key_initial]); + b[1]([]); + } + }; + ret[key_get] = () => b[0]()[0]; + ret[key_set] = ret; + ret[key_proto] = proto_entity_writtable_non_resetable_value; + return ret; +}; + +const prop_factory_dirty_required_initial = (ctx) => { + const s = sel(() => !obj_equals(ctx[key_get](), ctx[key_initial]) ) + const ret = {}; + ret[key_get] = s[0]; + ret[key_proto] = proto_entity_readable; + return ret; +}; + +const trait_ent_update = (ctx, fn) => (ctx[key_set](fn && fn(ctx[key_get]()))); +const trait_ent_update_by = (ctx, fn) => console.log("update_by", fn); +const trait_ent_sync = (ctx, fn) => { + const sync = () => { + e[0](); + fn(ctx[key_get]()); + }; + const e = expr(ctx[key_get], sync); + sync(); + return ctx; +}; + +const proto_entity_readable = obj_create(proto_base_pure_fn); +proto_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); + +const proto_entity_writtable_update_ns = obj_create(proto_base_pure_fn); +proto_def_prop_trait(proto_entity_writtable_update_ns, "by", trait_ent_update_by); + +const proto_entity_writtable = obj_create(proto_base_pure_fn); +proto_def_prop_trait(proto_entity_writtable, key_sync, trait_ent_sync); +proto_def_prop_trait_with_ns( + proto_entity_writtable, + "update", + trait_ent_update, + proto_entity_writtable_update_ns +); +proto_def_prop_factory( + proto_entity_writtable, + key_dirty, + prop_factory_dirty_required_initial +); + + +const proto_entity_writtable_non_resetable_value = obj_create( + proto_entity_writtable +); +proto_def_prop_promise(proto_entity_writtable_non_resetable_value); + +const proto_entity_writtable_resetable = obj_create(proto_entity_writtable); +proto_def_prop_factory( + proto_entity_writtable_resetable, + key_reset, + prop_factory_reset_required_promise_and_initial +); + +const proto_entity_writtable_resetable_value = obj_create( + proto_entity_writtable_resetable +); +proto_def_prop_promise(proto_entity_writtable_resetable_value); + +const proto_entity_writtable_resetable_value_trigger = obj_create( + proto_entity_writtable_resetable +); +proto_def_prop_promise_for_trigger( + proto_entity_writtable_resetable_value_trigger +); + +const make_entity_writtable_with_initial = (ctx, initial, writtable_proto) => { + const ret = ctx[1]; + ret[key_proto] = writtable_proto; + ret[key_get] = ctx[0]; + ret[key_set] = ctx[1]; + ret[key_initial] = initial; + return ret; +}; + +export const _value = (initial) => { + const b = box(initial); + return make_entity_writtable_with_initial( + b, + initial, + proto_entity_writtable_resetable_value + ); +}; + +export const _value_trigger = (initial) => { + const b = box(initial); + return make_entity_writtable_with_initial( + b, + initial, + proto_entity_writtable_resetable_value_trigger + ); +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type Ensurable = T | void; diff --git a/tests/_api.test.ts b/tests/_api.test.ts new file mode 100644 index 00000000..db01124a --- /dev/null +++ b/tests/_api.test.ts @@ -0,0 +1,127 @@ +import { _value } from '../src'; + +test('should work _value with call, get, set, update, sync', () => { + const spy = jest.fn(); + let t, r; + const v = _value(0); + const get = v.get; + + expect(get()).toBe(0); + t = v.sync; t(spy); + + expect(spy).toHaveBeenCalledWith(0); + spy.mockReset(); + + t = v.update; r = t(v => v + 1); + expect(get()).toBe(1); + expect(spy).toHaveBeenCalledWith(1); + spy.mockReset(); + expect(r).toBeUndefined(); + + t = v.set; t(10); + expect(get()).toBe(10); + expect(spy).toHaveBeenCalledWith(10); + spy.mockReset(); + + r = v(11); + expect(r).toBeUndefined(); + expect(get()).toBe(11); + expect(spy).toHaveBeenNthCalledWith(1, 11); + v.call(null, 12); + expect(get()).toBe(12); + expect(spy).toHaveBeenNthCalledWith(2, 12); + v.apply(null, [7]); + expect(get()).toBe(7); + expect(spy).toHaveBeenNthCalledWith(3, 7); + spy.mockReset(); +}); + +test('should work _value with reset', () => { + const spy_value = jest.fn(); + const spy_reset = jest.fn(); + const v = _value(0); + + v.sync(spy_value); + v.reset.sync(spy_reset); + + expect(spy_value).toHaveBeenCalledWith(0); spy_value.mockReset(); + expect(spy_reset).toHaveBeenCalled(); spy_reset.mockReset(); + + v.reset(); + v.reset.set(); + v.reset.update(); + expect(v.reset.get()).toBeUndefined(); + expect(spy_reset).toBeCalledTimes(0); + + v(5); + expect(spy_value).toHaveBeenCalledWith(5); spy_value.mockReset(); + expect(v.reset.get()).toBeUndefined(); + v(0); + v.reset(); + expect(spy_reset).toBeCalledTimes(0); + v(10); + spy_value.mockReset(); + + v.reset(); + expect(spy_value).toHaveBeenCalledWith(0); spy_value.mockReset(); + expect(spy_reset).toHaveBeenCalledTimes(1); + v.reset.set(); + v.reset.update(); + expect(spy_reset).toHaveBeenCalledTimes(1); spy_reset.mockReset(); + + v(5); + v.reset.set(); + expect(spy_reset).toHaveBeenCalledTimes(1); spy_reset.mockReset(); + v(5); + v.reset.update(); + expect(spy_reset).toHaveBeenCalledTimes(1); spy_reset.mockReset(); +}); + + +test('should work _value with dirty', () => { + const spy = jest.fn(); + const v = _value(0); + const dirty = v.dirty; + + expect(typeof dirty).toBe('object'); + expect(dirty.update).toBeUndefined(); + expect(dirty.set).toBeUndefined(); + expect(dirty.dirty).toBeUndefined(); + expect(dirty.get).not.toBeUndefined(); + + const dirty_sync = dirty.sync; + dirty_sync(spy); + + expect(spy).toHaveBeenCalledWith(false); spy.mockReset(); + v(5); + v(6); + v(5); + expect(spy).toHaveBeenCalledWith(true); + expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); + v(0); + expect(spy).toHaveBeenCalledWith(false); spy.mockReset(); + v(10); + expect(spy).toHaveBeenCalledWith(true); spy.mockReset(); + v.reset(); + expect(spy).toHaveBeenCalledWith(false); spy.mockReset(); + v(0); + v.reset(); + expect(spy).toHaveBeenCalledTimes(0); +}); + + + + + + + + + + + + + + + + + diff --git a/tsconfig.json b/tsconfig.json index 16d182e4..4212f0e7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "outDir": "./build", - "noImplicitAny": true, + "noImplicitAny": false, "module": "commonjs", "target": "es2015", "jsx": "react", From 3bba6ac6d018f16d42652c90076baa45889b1eb8 Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 14 May 2021 14:58:37 +0700 Subject: [PATCH 002/100] chore: todos updated --- src/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/index.ts b/src/index.ts index 64f6aff4..e5d612e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,6 +92,19 @@ const def_prop = Object.defineProperty; +/* + TODOs: + [] prev_value to sync + [] select + [] value.trigger + [] value.from + [] ... + + +*/ + + + From 94ebb0dcbbb238044698dd68e85deffa26e0669b Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 14 May 2021 15:09:13 +0700 Subject: [PATCH 003/100] chore: some updates --- src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index e5d612e7..234779a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,6 +95,7 @@ const def_prop = Object.defineProperty; /* TODOs: [] prev_value to sync + [] update.by [] select [] value.trigger [] value.from @@ -158,8 +159,9 @@ const proto_def_prop_promise = (obj) => { const ctx = this; if (!ctx[key_promise_internal]) { ctx[key_promise_internal] = new Promise((resolve) => + // TODO: should be the highest priority. expr(ctx[key_get], () => { - ctx[key_promise_internal] = void 0; + ctx[key_promise_internal] = 0; resolve(ctx[key_get]()); })[0]() ); From c2194fe110bb10200bdd66bc6f955f4f08ae6d4d Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 10:33:59 +0700 Subject: [PATCH 004/100] chore: some updates --- src/index.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 234779a9..4a9697f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,13 +95,22 @@ const def_prop = Object.defineProperty; /* TODOs: [] prev_value to sync - [] update.by + [] .val + [] .initial -- value with binded change_listener + [] update.by -> ctx + [] reset.by -> ctx [] select [] value.trigger [] value.from + [] signal + [] v.as.value(), v.as.signal() + [] .pre + [] .combine + [] .join + [] .chan + [] .to [] ... - */ @@ -234,11 +243,6 @@ proto_def_prop_trait_with_ns( trait_ent_update, proto_entity_writtable_update_ns ); -proto_def_prop_factory( - proto_entity_writtable, - key_dirty, - prop_factory_dirty_required_initial -); const proto_entity_writtable_non_resetable_value = obj_create( @@ -252,6 +256,11 @@ proto_def_prop_factory( key_reset, prop_factory_reset_required_promise_and_initial ); +proto_def_prop_factory( + proto_entity_writtable_resetable, + key_dirty, + prop_factory_dirty_required_initial +); const proto_entity_writtable_resetable_value = obj_create( proto_entity_writtable_resetable From 25311f90780ccf53e7257ace0130f2e9cf4d7c79 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 10:54:27 +0700 Subject: [PATCH 005/100] feat: prev_value added to sync --- src/index.ts | 44 ++++++++++++++++++++++++++++++++++++-------- tests/_api.test.ts | 28 ++++++++++++++-------------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4a9697f3..3ad78ebb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,7 +94,6 @@ const def_prop = Object.defineProperty; /* TODOs: - [] prev_value to sync [] .val [] .initial -- value with binded change_listener [] update.by -> ctx @@ -151,6 +150,8 @@ const proto_def_prop_factory = (obj, key, factory) => } }); + + const proto_base_pure_fn = function () {}; const key_get = "get"; @@ -162,7 +163,9 @@ const key_initial = Symbol(); const key_dirty = "dirty"; const key_sync = "sync"; -const proto_def_prop_promise = (obj) => { + + +const proto_def_prop_promise_for_non_trigger = (obj) => { return obj_def_prop(obj, key_promise, { get() { const ctx = this; @@ -217,24 +220,38 @@ const prop_factory_dirty_required_initial = (ctx) => { return ret; }; + + const trait_ent_update = (ctx, fn) => (ctx[key_set](fn && fn(ctx[key_get]()))); -const trait_ent_update_by = (ctx, fn) => console.log("update_by", fn); +const trait_ent_update_by = (ctx, fn) => { + console.log("update_by", fn); + return ctx; +}; const trait_ent_sync = (ctx, fn) => { + let prev_value; const sync = () => { - e[0](); - fn(ctx[key_get]()); + try { fn(ctx[key_get](), prev_value); } + finally { prev_value = e[0](); } }; const e = expr(ctx[key_get], sync); sync(); return ctx; }; + +// readable +// .sync const proto_entity_readable = obj_create(proto_base_pure_fn); proto_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); +// writtable.update:ns +// .update.by const proto_entity_writtable_update_ns = obj_create(proto_base_pure_fn); proto_def_prop_trait(proto_entity_writtable_update_ns, "by", trait_ent_update_by); +// writtable +// .sync +// .update:writtable.update:ns const proto_entity_writtable = obj_create(proto_base_pure_fn); proto_def_prop_trait(proto_entity_writtable, key_sync, trait_ent_sync); proto_def_prop_trait_with_ns( @@ -244,12 +261,16 @@ proto_def_prop_trait_with_ns( proto_entity_writtable_update_ns ); - +// writtable_non_resetable <- writtable +// .promise `for non trigger const proto_entity_writtable_non_resetable_value = obj_create( proto_entity_writtable ); -proto_def_prop_promise(proto_entity_writtable_non_resetable_value); +proto_def_prop_promise_for_non_trigger(proto_entity_writtable_non_resetable_value); +// writtable_resetable <- writtable +// .reset +// .dirty const proto_entity_writtable_resetable = obj_create(proto_entity_writtable); proto_def_prop_factory( proto_entity_writtable_resetable, @@ -262,11 +283,15 @@ proto_def_prop_factory( prop_factory_dirty_required_initial ); +// writtable_resetable_value <- writtable_resetable +// .promise `for non trigger const proto_entity_writtable_resetable_value = obj_create( proto_entity_writtable_resetable ); -proto_def_prop_promise(proto_entity_writtable_resetable_value); +proto_def_prop_promise_for_non_trigger(proto_entity_writtable_resetable_value); +// writtable_resetable_value_trigger <- writtable_resetable +// .promise `for trigger const proto_entity_writtable_resetable_value_trigger = obj_create( proto_entity_writtable_resetable ); @@ -274,6 +299,9 @@ proto_def_prop_promise_for_trigger( proto_entity_writtable_resetable_value_trigger ); + + + const make_entity_writtable_with_initial = (ctx, initial, writtable_proto) => { const ret = ctx[1]; ret[key_proto] = writtable_proto; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index db01124a..ae6fc5c7 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -9,30 +9,30 @@ test('should work _value with call, get, set, update, sync', () => { expect(get()).toBe(0); t = v.sync; t(spy); - expect(spy).toHaveBeenCalledWith(0); + expect(spy).toHaveBeenCalledWith(0, void 0); spy.mockReset(); t = v.update; r = t(v => v + 1); expect(get()).toBe(1); - expect(spy).toHaveBeenCalledWith(1); + expect(spy).toHaveBeenCalledWith(1, 0); spy.mockReset(); expect(r).toBeUndefined(); t = v.set; t(10); expect(get()).toBe(10); - expect(spy).toHaveBeenCalledWith(10); + expect(spy).toHaveBeenCalledWith(10, 1); spy.mockReset(); r = v(11); expect(r).toBeUndefined(); expect(get()).toBe(11); - expect(spy).toHaveBeenNthCalledWith(1, 11); + expect(spy).toHaveBeenNthCalledWith(1, 11, 10); v.call(null, 12); expect(get()).toBe(12); - expect(spy).toHaveBeenNthCalledWith(2, 12); + expect(spy).toHaveBeenNthCalledWith(2, 12, 11); v.apply(null, [7]); expect(get()).toBe(7); - expect(spy).toHaveBeenNthCalledWith(3, 7); + expect(spy).toHaveBeenNthCalledWith(3, 7, 12); spy.mockReset(); }); @@ -44,7 +44,7 @@ test('should work _value with reset', () => { v.sync(spy_value); v.reset.sync(spy_reset); - expect(spy_value).toHaveBeenCalledWith(0); spy_value.mockReset(); + expect(spy_value).toHaveBeenCalledWith(0, void 0); spy_value.mockReset(); expect(spy_reset).toHaveBeenCalled(); spy_reset.mockReset(); v.reset(); @@ -54,7 +54,7 @@ test('should work _value with reset', () => { expect(spy_reset).toBeCalledTimes(0); v(5); - expect(spy_value).toHaveBeenCalledWith(5); spy_value.mockReset(); + expect(spy_value).toHaveBeenCalledWith(5, 0); spy_value.mockReset(); expect(v.reset.get()).toBeUndefined(); v(0); v.reset(); @@ -63,7 +63,7 @@ test('should work _value with reset', () => { spy_value.mockReset(); v.reset(); - expect(spy_value).toHaveBeenCalledWith(0); spy_value.mockReset(); + expect(spy_value).toHaveBeenCalledWith(0, 10); spy_value.mockReset(); expect(spy_reset).toHaveBeenCalledTimes(1); v.reset.set(); v.reset.update(); @@ -92,18 +92,18 @@ test('should work _value with dirty', () => { const dirty_sync = dirty.sync; dirty_sync(spy); - expect(spy).toHaveBeenCalledWith(false); spy.mockReset(); + expect(spy).toHaveBeenCalledWith(false, void 0); spy.mockReset(); v(5); v(6); v(5); - expect(spy).toHaveBeenCalledWith(true); + expect(spy).toHaveBeenCalledWith(true, false); expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); v(0); - expect(spy).toHaveBeenCalledWith(false); spy.mockReset(); + expect(spy).toHaveBeenCalledWith(false, true); spy.mockReset(); v(10); - expect(spy).toHaveBeenCalledWith(true); spy.mockReset(); + expect(spy).toHaveBeenCalledWith(true, false); spy.mockReset(); v.reset(); - expect(spy).toHaveBeenCalledWith(false); spy.mockReset(); + expect(spy).toHaveBeenCalledWith(false, true); spy.mockReset(); v(0); v.reset(); expect(spy).toHaveBeenCalledTimes(0); From b87e3896fdaffde477b54d2f47df261d30b910a9 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 11:01:08 +0700 Subject: [PATCH 006/100] chore: sync chaining test case added --- src/index.ts | 4 ++-- tests/_api.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3ad78ebb..6e9ce036 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,8 +104,8 @@ const def_prop = Object.defineProperty; [] signal [] v.as.value(), v.as.signal() [] .pre - [] .combine - [] .join + [] .combine // doubtful (use .val resolve instead) + [] .join // doubtful (use .val resolve instead) [] .chan [] .to [] ... diff --git a/tests/_api.test.ts b/tests/_api.test.ts index ae6fc5c7..a47e6a72 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -2,17 +2,17 @@ import { _value } from '../src'; test('should work _value with call, get, set, update, sync', () => { const spy = jest.fn(); - let t, r; + let t, r, k; const v = _value(0); const get = v.get; expect(get()).toBe(0); - t = v.sync; t(spy); + t = v.sync; k = t(spy); expect(spy).toHaveBeenCalledWith(0, void 0); spy.mockReset(); - t = v.update; r = t(v => v + 1); + t = k.update; r = t(v => v + 1); expect(get()).toBe(1); expect(spy).toHaveBeenCalledWith(1, 0); spy.mockReset(); From e178aa5c1dcc7640270ebf19f8835ed31ad0d44f Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 12:58:38 +0700 Subject: [PATCH 007/100] feat: update.by implemented for _value --- src/index.ts | 57 ++++++++++++++++++++++++++++------------------ tests/_api.test.ts | 21 ++++++++++++++++- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6e9ce036..78fedc81 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,7 +97,6 @@ const def_prop = Object.defineProperty; [] .val [] .initial -- value with binded change_listener [] update.by -> ctx - [] reset.by -> ctx [] select [] value.trigger [] value.from @@ -110,6 +109,11 @@ const def_prop = Object.defineProperty; [] .to [] ... + + Backlog (0.7 roadmap) + [] reset.by -> ctx (moved to 0.7 roadmap or change reset to funcation) + [] reset.to() (proposal to change initial value) + ~but its free for initial impl */ @@ -120,8 +124,19 @@ const def_prop = Object.defineProperty; const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; +const proto_base_pure_fn = function () {}; const key_proto = "__proto__"; +const key_get = "get"; +const key_set = "set"; +const key_promise = "promise"; +const key_promise_internal = Symbol(); +const key_reset = "reset"; +const key_initial = Symbol(); +const key_dirty = "dirty"; +const key_sync = "sync"; +const key_ctx = Symbol(); + const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -137,9 +152,10 @@ const proto_def_prop_trait = (obj, key, trait) => const proto_def_prop_trait_with_ns = (obj, key, trait, ns) => obj_def_prop(obj, key, { get() { - const binded = trait.bind(void 0, this); - binded[key_proto] = ns; - return obj_def_prop_value(this, key, binded); + const ret = trait.bind(void 0, this); + ret[key_proto] = ns; + ret[key_ctx] = this; + return obj_def_prop_value(this, key, ret); } }); @@ -150,21 +166,6 @@ const proto_def_prop_factory = (obj, key, factory) => } }); - - -const proto_base_pure_fn = function () {}; - -const key_get = "get"; -const key_set = "set"; -const key_promise = "promise"; -const key_promise_internal = Symbol(); -const key_reset = "reset"; -const key_initial = Symbol(); -const key_dirty = "dirty"; -const key_sync = "sync"; - - - const proto_def_prop_promise_for_non_trigger = (obj) => { return obj_def_prop(obj, key_promise, { get() { @@ -197,6 +198,8 @@ const proto_def_prop_promise_for_trigger = (obj) => { }); }; + + const prop_factory_reset_required_promise_and_initial = (ctx) => { const b = box([]); const ret = () => { @@ -223,9 +226,19 @@ const prop_factory_dirty_required_initial = (ctx) => { const trait_ent_update = (ctx, fn) => (ctx[key_set](fn && fn(ctx[key_get]()))); -const trait_ent_update_by = (ctx, fn) => { - console.log("update_by", fn); - return ctx; +const trait_ent_update_by = (ctx, src, fn) => { + const src_get = src[key_get] ? src[key_get] : src; + const e = expr(src_get, () => { + try { + ctx[key_ctx][key_set]( + fn + ? fn(ctx[key_ctx][key_get](), src_get(), prev_value) + : src_get() + ); + } finally { prev_value = e[0](); } + }); + let prev_value = e[0](); + return ctx[key_ctx]; }; const trait_ent_sync = (ctx, fn) => { let prev_value; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index a47e6a72..be893635 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -109,7 +109,26 @@ test('should work _value with dirty', () => { expect(spy).toHaveBeenCalledTimes(0); }); - +test('should work _value with update.by', () => { + const v = _value(1); + const k = _value(0); + const m = _value(0); + const p = _value(0); + + v + .update.by(k, (_v, _k, _k_prev) => _v * 100 + _k * 10 + _k_prev) + .update.by(() => m.get() + 1, (_v, _m, _m_prev) => _v * 1000 + _m * 10 + _m_prev) + .update.by(p); + expect(v.get()).toBe(1); + k(1); + expect(v.get()).toBe(110); + k(2); + expect(v.get()).toBe(11021); + m(5); + expect(v.get()).toBe(11021061); + p(10); + expect(v.get()).toBe(10); +}); From eefca2b3674bbcb880ab4291d15f35ad2b026e1f Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 13:39:59 +0700 Subject: [PATCH 008/100] feat: .val accessor implemented for _value --- src/index.ts | 101 +++++++++++++++++++++++---------------------- tests/_api.test.ts | 16 +++++++ 2 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/index.ts b/src/index.ts index 78fedc81..f2d9c505 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,10 +93,19 @@ const def_prop = Object.defineProperty; /* + // Default method .by for values + const v = value(0); + const k = value(2); + v.by(k) alias to reset.update.by; + + reinit, reset - usually signals/boxes + ... + + TODOs: - [] .val - [] .initial -- value with binded change_listener - [] update.by -> ctx + [] reinit(val) -> void + [] reinit.by(k) -> ctx + [] reset.by(k) called inside -> reset.update.by(k) -> ctx [] select [] value.trigger [] value.from @@ -111,7 +120,6 @@ const def_prop = Object.defineProperty; Backlog (0.7 roadmap) - [] reset.by -> ctx (moved to 0.7 roadmap or change reset to funcation) [] reset.to() (proposal to change initial value) ~but its free for initial impl */ @@ -142,14 +150,21 @@ const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value ); -const proto_def_prop_trait = (obj, key, trait) => +const obj_def_prop_trait = (obj, key, trait) => obj_def_prop(obj, key, { get() { return obj_def_prop_value(this, key, trait.bind(void 0, this)); } }); -const proto_def_prop_trait_with_ns = (obj, key, trait, ns) => +const obj_def_prop_trait_ns = (obj, key, trait) => + obj_def_prop(obj, key, { + get() { + return obj_def_prop_value(this, key, trait.bind(void 0, this[key_ctx])); + } + }); + +const obj_def_prop_trait_with_ns = (obj, key, trait, ns) => obj_def_prop(obj, key, { get() { const ret = trait.bind(void 0, this); @@ -159,14 +174,14 @@ const proto_def_prop_trait_with_ns = (obj, key, trait, ns) => } }); -const proto_def_prop_factory = (obj, key, factory) => +const obj_def_prop_factory = (obj, key, factory) => obj_def_prop(obj, key, { get() { return obj_def_prop_value(this, key, factory(this)); } }); -const proto_def_prop_promise_for_non_trigger = (obj) => { +const obj_def_prop_promise_for_non_trigger = (obj) => { return obj_def_prop(obj, key_promise, { get() { const ctx = this; @@ -184,7 +199,7 @@ const proto_def_prop_promise_for_non_trigger = (obj) => { }); }; -const proto_def_prop_promise_for_trigger = (obj) => { +const obj_def_prop_promise_for_trigger = (obj) => { return obj_def_prop(obj, key_promise, { get() { const ctx = this; @@ -209,18 +224,12 @@ const prop_factory_reset_required_promise_and_initial = (ctx) => { b[1]([]); } }; - ret[key_get] = () => b[0]()[0]; - ret[key_set] = ret; - ret[key_proto] = proto_entity_writtable_non_resetable_value; - return ret; + return fill_blank_entity(ret, proto_entity_writtable_non_resetable_value, () => b[0]()[0], ret); }; const prop_factory_dirty_required_initial = (ctx) => { const s = sel(() => !obj_equals(ctx[key_get](), ctx[key_initial]) ) - const ret = {}; - ret[key_get] = s[0]; - ret[key_proto] = proto_entity_readable; - return ret; + return fill_blank_entity({}, proto_entity_readable, s[0]); }; @@ -230,15 +239,15 @@ const trait_ent_update_by = (ctx, src, fn) => { const src_get = src[key_get] ? src[key_get] : src; const e = expr(src_get, () => { try { - ctx[key_ctx][key_set]( + ctx[key_set]( fn - ? fn(ctx[key_ctx][key_get](), src_get(), prev_value) + ? fn(ctx[key_get](), src_get(), prev_value) : src_get() ); } finally { prev_value = e[0](); } }); let prev_value = e[0](); - return ctx[key_ctx]; + return ctx; }; const trait_ent_sync = (ctx, fn) => { let prev_value; @@ -255,19 +264,19 @@ const trait_ent_sync = (ctx, fn) => { // readable // .sync const proto_entity_readable = obj_create(proto_base_pure_fn); -proto_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); +obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); // writtable.update:ns // .update.by const proto_entity_writtable_update_ns = obj_create(proto_base_pure_fn); -proto_def_prop_trait(proto_entity_writtable_update_ns, "by", trait_ent_update_by); +obj_def_prop_trait_ns(proto_entity_writtable_update_ns, "by", trait_ent_update_by); // writtable // .sync // .update:writtable.update:ns const proto_entity_writtable = obj_create(proto_base_pure_fn); -proto_def_prop_trait(proto_entity_writtable, key_sync, trait_ent_sync); -proto_def_prop_trait_with_ns( +obj_def_prop_trait(proto_entity_writtable, key_sync, trait_ent_sync); +obj_def_prop_trait_with_ns( proto_entity_writtable, "update", trait_ent_update, @@ -279,18 +288,18 @@ proto_def_prop_trait_with_ns( const proto_entity_writtable_non_resetable_value = obj_create( proto_entity_writtable ); -proto_def_prop_promise_for_non_trigger(proto_entity_writtable_non_resetable_value); +obj_def_prop_promise_for_non_trigger(proto_entity_writtable_non_resetable_value); // writtable_resetable <- writtable // .reset // .dirty const proto_entity_writtable_resetable = obj_create(proto_entity_writtable); -proto_def_prop_factory( +obj_def_prop_factory( proto_entity_writtable_resetable, key_reset, prop_factory_reset_required_promise_and_initial ); -proto_def_prop_factory( +obj_def_prop_factory( proto_entity_writtable_resetable, key_dirty, prop_factory_dirty_required_initial @@ -301,45 +310,39 @@ proto_def_prop_factory( const proto_entity_writtable_resetable_value = obj_create( proto_entity_writtable_resetable ); -proto_def_prop_promise_for_non_trigger(proto_entity_writtable_resetable_value); +obj_def_prop_promise_for_non_trigger(proto_entity_writtable_resetable_value); // writtable_resetable_value_trigger <- writtable_resetable // .promise `for trigger const proto_entity_writtable_resetable_value_trigger = obj_create( proto_entity_writtable_resetable ); -proto_def_prop_promise_for_trigger( +obj_def_prop_promise_for_trigger( proto_entity_writtable_resetable_value_trigger ); - - -const make_entity_writtable_with_initial = (ctx, initial, writtable_proto) => { - const ret = ctx[1]; - ret[key_proto] = writtable_proto; - ret[key_get] = ctx[0]; - ret[key_set] = ctx[1]; - ret[key_initial] = initial; - return ret; -}; +const fill_blank_entity = (ctx, proto, get, set?, initial?) => { + ctx[key_proto] = proto; + ctx[key_get] = get; + if (!set) { + obj_def_prop(ctx, key, { get }); + } else { + ctx[key_set] = set; + obj_def_prop(ctx, key, { get, set }); + ctx[key_initial] = initial; + } + return ctx; +} export const _value = (initial) => { const b = box(initial); - return make_entity_writtable_with_initial( - b, - initial, - proto_entity_writtable_resetable_value - ); + return fill_blank_entity(b[1], proto_entity_writtable_resetable_value, b[0], b[1], initial); }; export const _value_trigger = (initial) => { const b = box(initial); - return make_entity_writtable_with_initial( - b, - initial, - proto_entity_writtable_resetable_value_trigger - ); + return fill_blank_entity(b[1], proto_entity_writtable_resetable_value_trigger, b[0], b[1], initial); }; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index be893635..22bef338 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -130,6 +130,22 @@ test('should work _value with update.by', () => { expect(v.get()).toBe(10); }); +test('should work _value with val', () => { + const v = _value(1); + expect(v.val).toBe(1); + v.val += 1; + expect(v.val).toBe(2); + expect(v.dirty.val).toBe(true); + expect(v.reset.val).toBeUndefined(); + + expect(() => { + v.dirty.val = true; + }).toThrow('Cannot set property val of [object Object] which has only a getter'); + + v.reset.val = 'anything'; + expect(v.val).toBe(1); + expect(v.dirty.val).toBe(false); +}); From b1630b0691ed3de6694c19b90a79cfb058a973e2 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 13:53:53 +0700 Subject: [PATCH 009/100] chore: todos updated --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index f2d9c505..a135cc09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,6 +101,8 @@ const def_prop = Object.defineProperty; reinit, reset - usually signals/boxes ... + reinit, reset, update - usually functions, I gonna my flow simpler. + ... TODOs: [] reinit(val) -> void @@ -112,6 +114,7 @@ const def_prop = Object.defineProperty; [] signal [] v.as.value(), v.as.signal() [] .pre + [] .view [] .combine // doubtful (use .val resolve instead) [] .join // doubtful (use .val resolve instead) [] .chan From 24b6278205240cb83146389cd6fea59c7f80d6ea Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 15:01:50 +0700 Subject: [PATCH 010/100] feat: reset updated, reinit implemented --- src/index.ts | 143 +++++++++++++++++++++++++++------------------ tests/_api.test.ts | 79 ++++++++++++++++--------- 2 files changed, 139 insertions(+), 83 deletions(-) diff --git a/src/index.ts b/src/index.ts index a135cc09..b49ffb8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -105,9 +105,7 @@ const def_prop = Object.defineProperty; ... TODOs: - [] reinit(val) -> void - [] reinit.by(k) -> ctx - [] reset.by(k) called inside -> reset.update.by(k) -> ctx + [] .to [] select [] value.trigger [] value.from @@ -118,7 +116,6 @@ const def_prop = Object.defineProperty; [] .combine // doubtful (use .val resolve instead) [] .join // doubtful (use .val resolve instead) [] .chan - [] .to [] ... @@ -144,9 +141,14 @@ const key_promise = "promise"; const key_promise_internal = Symbol(); const key_reset = "reset"; const key_initial = Symbol(); +const key_initial_accessor_flag = Symbol(); const key_dirty = "dirty"; const key_sync = "sync"; const key_ctx = Symbol(); +const key_by = "by"; +const key_reinit = "reinit"; +const key_update = "update"; +const key_val = "val"; const obj_def_prop_value = (obj, key, value) => ( @@ -217,22 +219,28 @@ const obj_def_prop_promise_for_trigger = (obj) => { }; +const fill_entity = (ctx, proto, get, set?, initial?) => { + ctx[key_proto] = proto; + ctx[key_get] = get; + if (!set) { + obj_def_prop(ctx, key_val, { get }); + } else { + ctx[key_set] = set; + obj_def_prop(ctx, key_val, { get, set }); + ctx[key_initial] = initial; + } + return ctx; +} -const prop_factory_reset_required_promise_and_initial = (ctx) => { - const b = box([]); - const ret = () => { - if (!obj_equals(ctx[key_initial], ctx[key_get]())) { - ctx[key_promise_internal] = 0; - ctx[key_set](ctx[key_initial]); - b[1]([]); - } - }; - return fill_blank_entity(ret, proto_entity_writtable_non_resetable_value, () => b[0]()[0], ret); -}; const prop_factory_dirty_required_initial = (ctx) => { + if (!ctx[key_initial_accessor_flag]) { + ctx[key_initial_accessor_flag] = 1; + const b = box(ctx[key_initial]); + obj_def_prop(ctx, key_initial, { get: b[0], set: b[1] }); + } const s = sel(() => !obj_equals(ctx[key_get](), ctx[key_initial]) ) - return fill_blank_entity({}, proto_entity_readable, s[0]); + return fill_entity({}, proto_entity_readable, s[0]); }; @@ -262,6 +270,33 @@ const trait_ent_sync = (ctx, fn) => { sync(); return ctx; }; +const trait_ent_reset = (ctx) => { + ctx[key_promise_internal] = 0; + ctx[key_set](ctx[key_initial]); +}; +const trait_ent_reset_by = (ctx, src) => { + const src_get = src[key_get] ? src[key_get] : src; + const e = expr(src_get, () => { + ctx[key_reset](); + e[0]() + }); + e[0](); + return ctx; +}; +const trait_ent_reinit = (ctx, initial) => { + ctx[key_initial] = initial; + ctx[key_reset](); +}; +const trait_ent_reinit_by = (ctx, src) => { + const src_get = src[key_get] ? src[key_get] : src; + const e = expr(src_get, () => { + ctx[key_reinit](src_get()); + e[0](); + }); + e[0](); + return ctx; +}; + // readable @@ -272,80 +307,76 @@ obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); // writtable.update:ns // .update.by const proto_entity_writtable_update_ns = obj_create(proto_base_pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_update_ns, "by", trait_ent_update_by); +obj_def_prop_trait_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by); + +// writtable.reset:ns +// .reset.by +const proto_entity_writtable_reset_ns = obj_create(proto_base_pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_reset_ns, key_by, trait_ent_reset_by); + +// writtable.reinit:ns +// .reinit.by +const proto_entity_writtable_reinit_ns = obj_create(proto_base_pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_reinit_ns, key_by, trait_ent_reinit_by); // writtable // .sync // .update:writtable.update:ns +// .reset:writtable.reset:ns +// .reinit:writtable.reinit:ns +// .dirty const proto_entity_writtable = obj_create(proto_base_pure_fn); obj_def_prop_trait(proto_entity_writtable, key_sync, trait_ent_sync); obj_def_prop_trait_with_ns( proto_entity_writtable, - "update", + key_update, trait_ent_update, proto_entity_writtable_update_ns ); - -// writtable_non_resetable <- writtable -// .promise `for non trigger -const proto_entity_writtable_non_resetable_value = obj_create( - proto_entity_writtable -); -obj_def_prop_promise_for_non_trigger(proto_entity_writtable_non_resetable_value); - -// writtable_resetable <- writtable -// .reset -// .dirty -const proto_entity_writtable_resetable = obj_create(proto_entity_writtable); -obj_def_prop_factory( - proto_entity_writtable_resetable, +obj_def_prop_trait_with_ns( + proto_entity_writtable, key_reset, - prop_factory_reset_required_promise_and_initial + trait_ent_reset, + proto_entity_writtable_reset_ns +); +obj_def_prop_trait_with_ns( + proto_entity_writtable, + key_reinit, + trait_ent_reinit, + proto_entity_writtable_reinit_ns ); obj_def_prop_factory( - proto_entity_writtable_resetable, + proto_entity_writtable, key_dirty, prop_factory_dirty_required_initial ); -// writtable_resetable_value <- writtable_resetable +// writtable_value <- writtable // .promise `for non trigger -const proto_entity_writtable_resetable_value = obj_create( - proto_entity_writtable_resetable +const proto_entity_writtable_value = obj_create( + proto_entity_writtable ); -obj_def_prop_promise_for_non_trigger(proto_entity_writtable_resetable_value); +obj_def_prop_promise_for_non_trigger(proto_entity_writtable_value); -// writtable_resetable_value_trigger <- writtable_resetable +// writtable_value_trigger <- writtable // .promise `for trigger -const proto_entity_writtable_resetable_value_trigger = obj_create( - proto_entity_writtable_resetable +const proto_entity_writtable_value_trigger = obj_create( + proto_entity_writtable ); obj_def_prop_promise_for_trigger( - proto_entity_writtable_resetable_value_trigger + proto_entity_writtable_value_trigger ); -const fill_blank_entity = (ctx, proto, get, set?, initial?) => { - ctx[key_proto] = proto; - ctx[key_get] = get; - if (!set) { - obj_def_prop(ctx, key, { get }); - } else { - ctx[key_set] = set; - obj_def_prop(ctx, key, { get, set }); - ctx[key_initial] = initial; - } - return ctx; -} export const _value = (initial) => { const b = box(initial); - return fill_blank_entity(b[1], proto_entity_writtable_resetable_value, b[0], b[1], initial); + return fill_entity(b[1], proto_entity_writtable_value, b[0], b[1], initial); }; export const _value_trigger = (initial) => { const b = box(initial); - return fill_blank_entity(b[1], proto_entity_writtable_resetable_value_trigger, b[0], b[1], initial); + return fill_entity(b[1], proto_entity_writtable_value_trigger, b[0], b[1], initial); }; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 22bef338..0a288cf9 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -38,45 +38,66 @@ test('should work _value with call, get, set, update, sync', () => { test('should work _value with reset', () => { const spy_value = jest.fn(); - const spy_reset = jest.fn(); const v = _value(0); + const k = _value(0); + const m = _value(0); + let t; v.sync(spy_value); - v.reset.sync(spy_reset); - expect(spy_value).toHaveBeenCalledWith(0, void 0); spy_value.mockReset(); - expect(spy_reset).toHaveBeenCalled(); spy_reset.mockReset(); - v.reset(); - v.reset.set(); - v.reset.update(); - expect(v.reset.get()).toBeUndefined(); - expect(spy_reset).toBeCalledTimes(0); + (t = v.reset); t(); + expect(spy_value).toBeCalledTimes(0); v(5); expect(spy_value).toHaveBeenCalledWith(5, 0); spy_value.mockReset(); - expect(v.reset.get()).toBeUndefined(); - v(0); - v.reset(); - expect(spy_reset).toBeCalledTimes(0); + expect(v.dirty.val).toBe(true); + + t(); + expect(v.dirty.val).toBe(false); + expect(spy_value).toHaveBeenCalledWith(0, 5); v(10); spy_value.mockReset(); - v.reset(); + (t = v.reset); (t = t.by); t(k).reset.by(() => m.val); + k(1); + expect(spy_value).toHaveBeenCalledWith(0, 10); v(10); spy_value.mockReset(); + m(1); + expect(spy_value).toHaveBeenCalledWith(0, 10); v(10); spy_value.mockReset(); +}); + +test('should work _value with reinit', () => { + const spy_value = jest.fn(); + const v = _value(0); + const k = _value(0); + const m = _value(0); + let t; + + v.sync(spy_value); + expect(spy_value).toHaveBeenCalledWith(0, void 0); spy_value.mockReset(); + + (t = v.reinit); t(10); + expect(spy_value).toHaveBeenCalledWith(10, 0); spy_value.mockReset(); + expect(v.dirty.val).toBe(false); + expect(v.val).toBe(10); + + v(0); expect(spy_value).toHaveBeenCalledWith(0, 10); spy_value.mockReset(); - expect(spy_reset).toHaveBeenCalledTimes(1); - v.reset.set(); - v.reset.update(); - expect(spy_reset).toHaveBeenCalledTimes(1); spy_reset.mockReset(); + expect(v.dirty.val).toBe(true); - v(5); - v.reset.set(); - expect(spy_reset).toHaveBeenCalledTimes(1); spy_reset.mockReset(); - v(5); - v.reset.update(); - expect(spy_reset).toHaveBeenCalledTimes(1); spy_reset.mockReset(); -}); + (t = v.reset); t(); + expect(v.dirty.val).toBe(false); + expect(spy_value).toHaveBeenCalledWith(10, 0); spy_value.mockReset(); + (t = v.reinit); (t = t.by); t(k).reinit.by(() => m.val); + k(1); + expect(spy_value).toHaveBeenCalledWith(1, 10); spy_value.mockReset(); + expect(v.dirty.val).toBe(false); + + m(5); + expect(spy_value).toHaveBeenCalledWith(5, 1); spy_value.mockReset(); + expect(v.dirty.val).toBe(false); +}); test('should work _value with dirty', () => { const spy = jest.fn(); @@ -107,6 +128,11 @@ test('should work _value with dirty', () => { v(0); v.reset(); expect(spy).toHaveBeenCalledTimes(0); + + v(10); + expect(dirty.val).toBe(true); + v.reinit(10); + expect(dirty.val).toBe(false); }); test('should work _value with update.by', () => { @@ -136,13 +162,12 @@ test('should work _value with val', () => { v.val += 1; expect(v.val).toBe(2); expect(v.dirty.val).toBe(true); - expect(v.reset.val).toBeUndefined(); expect(() => { v.dirty.val = true; }).toThrow('Cannot set property val of [object Object] which has only a getter'); - v.reset.val = 'anything'; + v.reset(); expect(v.val).toBe(1); expect(v.dirty.val).toBe(false); }); From 4f9d15f0665a50d1eef500378db0a94cb26a6377 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 15:03:50 +0700 Subject: [PATCH 011/100] chore: todos updated --- src/index.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/index.ts b/src/index.ts index b49ffb8d..75045168 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,17 +93,6 @@ const def_prop = Object.defineProperty; /* - // Default method .by for values - const v = value(0); - const k = value(2); - v.by(k) alias to reset.update.by; - - reinit, reset - usually signals/boxes - ... - - reinit, reset, update - usually functions, I gonna my flow simpler. - ... - TODOs: [] .to [] select @@ -117,11 +106,6 @@ const def_prop = Object.defineProperty; [] .join // doubtful (use .val resolve instead) [] .chan [] ... - - - Backlog (0.7 roadmap) - [] reset.to() (proposal to change initial value) - ~but its free for initial impl */ From 4b92e290d9b03271782630aa9cac791f63a59a12 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 16:04:11 +0700 Subject: [PATCH 012/100] feat: to and to.once added to _value --- src/index.ts | 47 +++++++++++++++++++++++++++++++++++++++------- tests/_api.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 75045168..615ed3c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,13 +95,16 @@ const def_prop = Object.defineProperty; /* TODOs: [] .to - [] select + [] .to.once + [] select(fn?) + [] .pre + [] .view + [] .flow [] value.trigger [] value.from [] signal [] v.as.value(), v.as.signal() - [] .pre - [] .view + [] v.as.readonly() [] .combine // doubtful (use .val resolve instead) [] .join // doubtful (use .val resolve instead) [] .chan @@ -133,6 +136,8 @@ const key_by = "by"; const key_reinit = "reinit"; const key_update = "update"; const key_val = "val"; +const key_once = "once"; +const key_to = "to"; const obj_def_prop_value = (obj, key, value) => ( @@ -280,13 +285,40 @@ const trait_ent_reinit_by = (ctx, src) => { e[0](); return ctx; }; +const trait_ent_to = (ctx, fn) => { + let prev_value; + const e = expr(ctx[key_get], () => { + try { fn(ctx[key_get](), prev_value); } + finally { prev_value = e[0](); } + }); + prev_value = e[0](); + return ctx; +}; +const trait_ent_to_once = (ctx, fn) => { + let prev_value; + const e = expr(ctx[key_get], () => fn(ctx[key_get](), prev_value)); + prev_value = e[0](); + return ctx; +}; +// readable.to:ns +// .to.once +const proto_entity_readable_to_ns = obj_create(proto_base_pure_fn); +obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); // readable // .sync +// .to:readable.to:ns +// .to.once const proto_entity_readable = obj_create(proto_base_pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); +obj_def_prop_trait_with_ns( + proto_entity_readable, + key_to, + trait_ent_to, + proto_entity_readable_to_ns +); // writtable.update:ns // .update.by @@ -303,14 +335,15 @@ obj_def_prop_trait_ns(proto_entity_writtable_reset_ns, key_by, trait_ent_reset_b const proto_entity_writtable_reinit_ns = obj_create(proto_base_pure_fn); obj_def_prop_trait_ns(proto_entity_writtable_reinit_ns, key_by, trait_ent_reinit_by); -// writtable -// .sync +// writtable <- readable // .update:writtable.update:ns +// .update.by // .reset:writtable.reset:ns +// .reset.by // .reinit:writtable.reinit:ns +// .reinit.by // .dirty -const proto_entity_writtable = obj_create(proto_base_pure_fn); -obj_def_prop_trait(proto_entity_writtable, key_sync, trait_ent_sync); +const proto_entity_writtable = obj_create(proto_entity_readable); obj_def_prop_trait_with_ns( proto_entity_writtable, key_update, diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 0a288cf9..562abcae 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -172,6 +172,29 @@ test('should work _value with val', () => { expect(v.dirty.val).toBe(false); }); +test('should work _value with to', () => { + const spy_to = jest.fn(); + const spy_to_once = jest.fn(); + const v = _value(0); + let t; + (t = v.to); t(spy_to); + (t = t.once); t(spy_to_once); + + expect(spy_to).toHaveBeenCalledTimes(0); + expect(spy_to_once).toHaveBeenCalledTimes(0); + + v(0); + expect(spy_to).toHaveBeenCalledTimes(0); + expect(spy_to_once).toHaveBeenCalledTimes(0); + + v(1); + expect(spy_to).toHaveBeenCalledWith(1, 0); spy_to.mockReset(); + expect(spy_to_once).toHaveBeenCalledWith(1, 0); spy_to_once.mockReset(); + + v(2); + expect(spy_to).toHaveBeenCalledWith(2, 1); spy_to.mockReset(); + expect(spy_to_once).toHaveBeenCalledTimes(0); +}); From ccc68d4068ca21acdbb425e2e250cadacf0bd391 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 16:15:08 +0700 Subject: [PATCH 013/100] feat: select added to _value --- src/index.ts | 13 ++++++++----- tests/_api.test.ts | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 615ed3c7..1fd64993 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,11 +94,8 @@ const def_prop = Object.defineProperty; /* TODOs: - [] .to - [] .to.once - [] select(fn?) - [] .pre [] .view + [] .pre [] .flow [] value.trigger [] value.from @@ -138,6 +135,7 @@ const key_update = "update"; const key_val = "val"; const key_once = "once"; const key_to = "to"; +const key_select = "select"; const obj_def_prop_value = (obj, key, value) => ( @@ -300,7 +298,10 @@ const trait_ent_to_once = (ctx, fn) => { prev_value = e[0](); return ctx; }; - +const trait_ent_select = (ctx, fn) => { + const s = sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]); + return fill_entity({}, proto_entity_readable, s[0]); +}; // readable.to:ns // .to.once @@ -311,6 +312,7 @@ obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); // .sync // .to:readable.to:ns // .to.once +// .select const proto_entity_readable = obj_create(proto_base_pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); obj_def_prop_trait_with_ns( @@ -319,6 +321,7 @@ obj_def_prop_trait_with_ns( trait_ent_to, proto_entity_readable_to_ns ); +obj_def_prop_trait(proto_entity_readable, key_select, trait_ent_select); // writtable.update:ns // .update.by diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 562abcae..3e0c9988 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -196,6 +196,27 @@ test('should work _value with to', () => { expect(spy_to_once).toHaveBeenCalledTimes(0); }); +test('should work _value with select', () => { + const spy = jest.fn(); + let t; + const v = _value(5); + const k = _value(0); + + (t = v.select); t((_v) => Math.abs(_v - k.val)).sync(spy); + + expect(spy).toHaveBeenCalledWith(5, void 0); + expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); + k(10); + expect(spy).toHaveBeenCalledTimes(0); + v(15); + expect(spy).toHaveBeenCalledTimes(0); + k(11); + expect(spy).toHaveBeenCalledWith(4, 5); + expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); + v(5); + expect(spy).toHaveBeenCalledWith(6, 4); + expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); +}); From 8bafb658f00d690664dd266a8f2df32f5864d3e2 Mon Sep 17 00:00:00 2001 From: betula Date: Sun, 16 May 2021 16:49:15 +0700 Subject: [PATCH 014/100] feat: _value.view design started --- src/index.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 1fd64993..24a37061 100644 --- a/src/index.ts +++ b/src/index.ts @@ -136,6 +136,7 @@ const key_val = "val"; const key_once = "once"; const key_to = "to"; const key_select = "select"; +const key_view = "view"; const obj_def_prop_value = (obj, key, value) => ( @@ -299,9 +300,26 @@ const trait_ent_to_once = (ctx, fn) => { return ctx; }; const trait_ent_select = (ctx, fn) => { - const s = sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]); + const s = sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]()); return fill_entity({}, proto_entity_readable, s[0]); }; +const trait_ent_view = (ctx, fn) => ( + // TODO: view should be inherit parent logic methods and state + // it can be proxy ...hm partial proxy + // it should have own to, to.once, and sync + // own update + // own view + + // But it should have dirty from inheritance + // And it should have pending from inheritance (custom fields) + // It should have reset, and reinit from inheritance + // + // * "from inheritance" means that access to those properties + // should proxy to already exist (instantiated) "parent" or base context + + + fill_entity({}, proto_entity_readable, () => fn ? fn(ctx[key_get]()) : ctx[key_get]()) +); // readable.to:ns // .to.once @@ -313,6 +331,7 @@ obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); // .to:readable.to:ns // .to.once // .select +// .view const proto_entity_readable = obj_create(proto_base_pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); obj_def_prop_trait_with_ns( @@ -322,6 +341,7 @@ obj_def_prop_trait_with_ns( proto_entity_readable_to_ns ); obj_def_prop_trait(proto_entity_readable, key_select, trait_ent_select); +obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); // writtable.update:ns // .update.by From 19b7fe0df74a8b80bf06d357fa731087aaaf96b1 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 17 May 2021 14:10:01 +0700 Subject: [PATCH 015/100] feat: key_handler added to _value preparation for view supposably finished --- src/index.ts | 65 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/src/index.ts b/src/index.ts index 24a37061..c9c24718 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,6 +106,8 @@ const def_prop = Object.defineProperty; [] .join // doubtful (use .val resolve instead) [] .chan [] ... + + Readonly signal has no pre, because pre depends on write */ @@ -137,6 +139,7 @@ const key_once = "once"; const key_to = "to"; const key_select = "select"; const key_view = "view"; +const key_handler = Symbol(); const obj_def_prop_value = (obj, key, value) => ( @@ -206,29 +209,36 @@ const obj_def_prop_promise_for_trigger = (obj) => { }); }; +const fill_entity = (handler, has_set, proto, initial?) => { + const set = handler[1]; + const get = handler[0]; + handler[key_initial] = initial; -const fill_entity = (ctx, proto, get, set?, initial?) => { - ctx[key_proto] = proto; - ctx[key_get] = get; - if (!set) { - obj_def_prop(ctx, key_val, { get }); - } else { + let ctx; + if (has_set) { + ctx = set; ctx[key_set] = set; obj_def_prop(ctx, key_val, { get, set }); - ctx[key_initial] = initial; + } else { + ctx = {}; + obj_def_prop(ctx, key_val, { get }) } + ctx[key_handler] = handler; + ctx[key_proto] = proto; + ctx[key_get] = get; return ctx; } const prop_factory_dirty_required_initial = (ctx) => { - if (!ctx[key_initial_accessor_flag]) { - ctx[key_initial_accessor_flag] = 1; - const b = box(ctx[key_initial]); - obj_def_prop(ctx, key_initial, { get: b[0], set: b[1] }); + const h = ctx[key_handler]; + if (!h[key_initial_accessor_flag]) { + h[key_initial_accessor_flag] = 1; + const b = box(h[key_initial]); + obj_def_prop(h, key_initial, { get: b[0], set: b[1] }); } - const s = sel(() => !obj_equals(ctx[key_get](), ctx[key_initial]) ) - return fill_entity({}, proto_entity_readable, s[0]); + const s = sel(() => !obj_equals(h[0](), h[key_initial]) ) + return fill_entity(s, 0, proto_entity_readable); }; @@ -260,7 +270,7 @@ const trait_ent_sync = (ctx, fn) => { }; const trait_ent_reset = (ctx) => { ctx[key_promise_internal] = 0; - ctx[key_set](ctx[key_initial]); + ctx[key_handler][1](ctx[key_handler][key_initial]); }; const trait_ent_reset_by = (ctx, src) => { const src_get = src[key_get] ? src[key_get] : src; @@ -272,7 +282,7 @@ const trait_ent_reset_by = (ctx, src) => { return ctx; }; const trait_ent_reinit = (ctx, initial) => { - ctx[key_initial] = initial; + ctx[key_handler][key_initial] = initial; ctx[key_reset](); }; const trait_ent_reinit_by = (ctx, src) => { @@ -299,26 +309,27 @@ const trait_ent_to_once = (ctx, fn) => { prev_value = e[0](); return ctx; }; -const trait_ent_select = (ctx, fn) => { - const s = sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]()); - return fill_entity({}, proto_entity_readable, s[0]); -}; +const trait_ent_select = (ctx, fn) => ( + fill_entity(sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]()), 0, proto_entity_readable) +); const trait_ent_view = (ctx, fn) => ( // TODO: view should be inherit parent logic methods and state // it can be proxy ...hm partial proxy // it should have own to, to.once, and sync // own update // own view + // own select // But it should have dirty from inheritance // And it should have pending from inheritance (custom fields) // It should have reset, and reinit from inheritance + // promise // // * "from inheritance" means that access to those properties // should proxy to already exist (instantiated) "parent" or base context - fill_entity({}, proto_entity_readable, () => fn ? fn(ctx[key_get]()) : ctx[key_get]()) + fill_entity([() => fn ? fn(ctx[key_get]()) : ctx[key_get]()], 0, proto_entity_readable) ); // readable.to:ns @@ -409,15 +420,13 @@ obj_def_prop_promise_for_trigger( -export const _value = (initial) => { - const b = box(initial); - return fill_entity(b[1], proto_entity_writtable_value, b[0], b[1], initial); -}; +export const _value = (initial) => ( + fill_entity(box(initial), 1, proto_entity_writtable_value, initial) +); -export const _value_trigger = (initial) => { - const b = box(initial); - return fill_entity(b[1], proto_entity_writtable_value_trigger, b[0], b[1], initial); -}; +export const _value_trigger = (initial) => ( + fill_entity(box(initial), 1, proto_entity_writtable_value_trigger, initial) +) From a4831373c94edf26a813efac40ed15936fd5387b Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 17 May 2021 14:49:41 +0700 Subject: [PATCH 016/100] feat: view added to _value --- src/index.ts | 51 ++++++++++++++++++---------------------------- tests/_api.test.ts | 30 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/index.ts b/src/index.ts index c9c24718..95f78db2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,7 +94,6 @@ const def_prop = Object.defineProperty; /* TODOs: - [] .view [] .pre [] .flow [] value.trigger @@ -127,7 +126,7 @@ const key_promise = "promise"; const key_promise_internal = Symbol(); const key_reset = "reset"; const key_initial = Symbol(); -const key_initial_accessor_flag = Symbol(); +const key_dirty_handler = Symbol(); const key_dirty = "dirty"; const key_sync = "sync"; const key_ctx = Symbol(); @@ -209,13 +208,13 @@ const obj_def_prop_promise_for_trigger = (obj) => { }); }; -const fill_entity = (handler, has_set, proto, initial?) => { - const set = handler[1]; - const get = handler[0]; - handler[key_initial] = initial; +const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { + const set = _set || handler[1]; + const get = _get || handler[0]; + has_initial && (handler[key_initial] = initial); let ctx; - if (has_set) { + if (set) { ctx = set; ctx[key_set] = set; obj_def_prop(ctx, key_val, { get, set }); @@ -232,13 +231,15 @@ const fill_entity = (handler, has_set, proto, initial?) => { const prop_factory_dirty_required_initial = (ctx) => { const h = ctx[key_handler]; - if (!h[key_initial_accessor_flag]) { - h[key_initial_accessor_flag] = 1; + if (!h[key_dirty_handler]) { const b = box(h[key_initial]); obj_def_prop(h, key_initial, { get: b[0], set: b[1] }); + + h[key_dirty_handler] = sel( + () => !obj_equals(h[0](), h[key_initial]) + ).slice(0, 1); } - const s = sel(() => !obj_equals(h[0](), h[key_initial]) ) - return fill_entity(s, 0, proto_entity_readable); + return fill_entity(h[key_dirty_handler], proto_entity_readable); }; @@ -310,26 +311,14 @@ const trait_ent_to_once = (ctx, fn) => { return ctx; }; const trait_ent_select = (ctx, fn) => ( - fill_entity(sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]()), 0, proto_entity_readable) + fill_entity(sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]()).slice(0, 1), proto_entity_readable) ); const trait_ent_view = (ctx, fn) => ( - // TODO: view should be inherit parent logic methods and state - // it can be proxy ...hm partial proxy - // it should have own to, to.once, and sync - // own update - // own view - // own select - - // But it should have dirty from inheritance - // And it should have pending from inheritance (custom fields) - // It should have reset, and reinit from inheritance - // promise - // - // * "from inheritance" means that access to those properties - // should proxy to already exist (instantiated) "parent" or base context - - - fill_entity([() => fn ? fn(ctx[key_get]()) : ctx[key_get]()], 0, proto_entity_readable) + fill_entity(ctx[key_handler], ctx[key_proto], + 0, 0, + () => fn ? fn(ctx[key_get]()) : ctx[key_get](), + ctx[key_set].bind() + ) ); // readable.to:ns @@ -421,11 +410,11 @@ obj_def_prop_promise_for_trigger( export const _value = (initial) => ( - fill_entity(box(initial), 1, proto_entity_writtable_value, initial) + fill_entity(box(initial), proto_entity_writtable_value, 1, initial) ); export const _value_trigger = (initial) => ( - fill_entity(box(initial), 1, proto_entity_writtable_value_trigger, initial) + fill_entity(box(initial), proto_entity_writtable_value_trigger, 1, initial) ) diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 3e0c9988..da04bfd4 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -218,6 +218,36 @@ test('should work _value with select', () => { expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); }); +test('should work _value with view', () => { + const spy = jest.fn(); + let t; + const v = _value(5); + const w = ((t = v.view), t((_v) => _v + _v)); + expect(w.val).toBe(10); + w.val = 16; + expect(w.val).toBe(32); + w.val = 5; + expect(w.val).toBe(10); + expect(v.val).toBe(5); + + (t = w.sync); t(spy); + expect(spy).toHaveBeenCalledWith(10, void 0); spy.mockReset(); + expect(w.dirty.val).toBe(false); + expect(v.dirty.val).toBe(false); + w(10); + expect(w.val).toBe(20); + + expect(spy).toHaveBeenCalledWith(20, 10); spy.mockReset(); + expect(w.dirty.val).toBe(true); + expect(v.dirty.val).toBe(true); + v(5); + expect(w.val).toBe(10); + + expect(spy).toHaveBeenCalledWith(10, 20); spy.mockReset(); + expect(w.dirty.val).toBe(false); + expect(v.dirty.val).toBe(false); +}); + From 29f167c3533c8a89eb2f59562a1183246984b5a0 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 17 May 2021 14:55:09 +0700 Subject: [PATCH 017/100] chore: todos updated --- src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 95f78db2..35aef723 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,7 +95,7 @@ const def_prop = Object.defineProperty; /* TODOs: [] .pre - [] .flow + [] .flow // basic/started impl without resolve and stop signal [] value.trigger [] value.from [] signal @@ -107,6 +107,8 @@ const def_prop = Object.defineProperty; [] ... Readonly signal has no pre, because pre depends on write + Backlog + [] .view.untrack */ From 6e24831dc2590d3d63ba8c882e25e62c57d8b5d6 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 17 May 2021 14:59:03 +0700 Subject: [PATCH 018/100] chore: the reactive box dependency version up --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2edb5cff..13ed1a96 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "clear": "rimraf build" }, "dependencies": { - "reactive-box": "0.6.6" + "reactive-box": "0.7.1" }, "devDependencies": { "@babel/core": "7.12.10", From 7be5bc22a0907e10b60c271552cf4dae5619287b Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 17 May 2021 17:01:16 +0700 Subject: [PATCH 019/100] feat: pre, pre.filter, pre.filter.not added to _value --- package.json | 2 +- src/index.ts | 108 ++++++++++++++++++++++++++++++++++++--------- tests/_api.test.ts | 70 +++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 13ed1a96..47c35825 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "clear": "rimraf build" }, "dependencies": { - "reactive-box": "0.7.1" + "reactive-box": "0.7.2" }, "devDependencies": { "@babel/core": "7.12.10", diff --git a/src/index.ts b/src/index.ts index 35aef723..c8f382ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import React, { Context, FC } from 'react'; -import { expr, box, sel, transaction, untrack } from 'reactive-box'; +import { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; export { value, @@ -94,7 +94,6 @@ const def_prop = Object.defineProperty; /* TODOs: - [] .pre [] .flow // basic/started impl without resolve and stop signal [] value.trigger [] value.from @@ -106,9 +105,11 @@ const def_prop = Object.defineProperty; [] .chan [] ... - Readonly signal has no pre, because pre depends on write Backlog [] .view.untrack + [] .pre.untrack + [] .pre.filter.untrack + [] .pre.filter.not.untrack */ @@ -119,7 +120,10 @@ const def_prop = Object.defineProperty; const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; -const proto_base_pure_fn = function () {}; + +const pure_fn = function () {}; +const pure_arrow_fn_returns_arg = (v) => v; +const pure_arrow_fn_returns_not_arg = (v) => !v; const key_proto = "__proto__"; const key_get = "get"; @@ -141,6 +145,9 @@ const key_to = "to"; const key_select = "select"; const key_view = "view"; const key_handler = Symbol(); +const key_pre = "pre"; +const key_filter = "filter"; +const key_not = "not"; const obj_def_prop_value = (obj, key, value) => ( @@ -161,6 +168,17 @@ const obj_def_prop_trait_ns = (obj, key, trait) => } }); +const obj_def_prop_trait_ns_with_ns = (obj, key, trait, ns) => + obj_def_prop(obj, key, { + get() { + const ctx = this[key_ctx]; + const ret = trait.bind(void 0, ctx); + ret[key_proto] = ns; + ret[key_ctx] = ctx; + return obj_def_prop_value(this, key, ret); + } + }); + const obj_def_prop_trait_with_ns = (obj, key, trait, ns) => obj_def_prop(obj, key, { get() { @@ -249,15 +267,14 @@ const prop_factory_dirty_required_initial = (ctx) => { const trait_ent_update = (ctx, fn) => (ctx[key_set](fn && fn(ctx[key_get]()))); const trait_ent_update_by = (ctx, src, fn) => { const src_get = src[key_get] ? src[key_get] : src; - const e = expr(src_get, () => { - try { - ctx[key_set]( - fn - ? fn(ctx[key_get](), src_get(), prev_value) - : src_get() - ); - } finally { prev_value = e[0](); } - }); + const e = expr(src_get, fn + ? () => { + try { + ctx[key_set](fn(ctx[key_get](), src_get(), prev_value)); + } finally { prev_value = e[0](); } + } + : () => (ctx[key_set](src_get()), (prev_value = e[0]())) + ); let prev_value = e[0](); return ctx; }; @@ -313,19 +330,45 @@ const trait_ent_to_once = (ctx, fn) => { return ctx; }; const trait_ent_select = (ctx, fn) => ( - fill_entity(sel(() => fn ? fn(ctx[key_get]()) : ctx[key_get]()).slice(0, 1), proto_entity_readable) + fill_entity(sel(fn ? () => fn(ctx[key_get]()) : ctx[key_get]).slice(0, 1), proto_entity_readable) ); const trait_ent_view = (ctx, fn) => ( fill_entity(ctx[key_handler], ctx[key_proto], 0, 0, - () => fn ? fn(ctx[key_get]()) : ctx[key_get](), + fn ? () => fn(ctx[key_get]()) : ctx[key_get], ctx[key_set].bind() ) ); +const trait_ent_pre = (ctx, fn) => ( + fn + ? fill_entity(ctx[key_handler], ctx[key_proto], + 0, 0, + ctx[key_get], + (v) => ctx[key_set](fn(v)) + ) + : ctx +); +const trait_ent_pre_filter = (ctx, fn) => ( + (fn = fn + ? (fn[key_get] ? fn[key_get] : fn) + : pure_arrow_fn_returns_arg + ), fill_entity(ctx[key_handler], ctx[key_proto], + 0, 0, + ctx[key_get], + (v) => fn(v) && ctx[key_set](v) + ) +); +const trait_ent_pre_filter_not = (ctx, fn) => ( + ctx[key_pre][key_filter](fn + ? (fn[key_get] + ? (v) => !fn[key_get](v) + : (v) => !fn(v)) + : pure_arrow_fn_returns_not_arg) +); // readable.to:ns // .to.once -const proto_entity_readable_to_ns = obj_create(proto_base_pure_fn); +const proto_entity_readable_to_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); // readable @@ -334,7 +377,7 @@ obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); // .to.once // .select // .view -const proto_entity_readable = obj_create(proto_base_pure_fn); +const proto_entity_readable = obj_create(pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); obj_def_prop_trait_with_ns( proto_entity_readable, @@ -347,19 +390,34 @@ obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); // writtable.update:ns // .update.by -const proto_entity_writtable_update_ns = obj_create(proto_base_pure_fn); +const proto_entity_writtable_update_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by); // writtable.reset:ns // .reset.by -const proto_entity_writtable_reset_ns = obj_create(proto_base_pure_fn); +const proto_entity_writtable_reset_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_writtable_reset_ns, key_by, trait_ent_reset_by); // writtable.reinit:ns // .reinit.by -const proto_entity_writtable_reinit_ns = obj_create(proto_base_pure_fn); +const proto_entity_writtable_reinit_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_writtable_reinit_ns, key_by, trait_ent_reinit_by); +// writtable.pre.filter:ns +// .pre.filter.not +const proto_entity_writtable_pre_filter_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_pre_filter_ns, key_not, trait_ent_pre_filter_not); + +// writtable.pre:ns +// .pre.filter +const proto_entity_writtable_pre_ns = obj_create(pure_fn); +obj_def_prop_trait_ns_with_ns( + proto_entity_writtable_pre_ns, + key_filter, + trait_ent_pre_filter, + proto_entity_writtable_pre_filter_ns +); + // writtable <- readable // .update:writtable.update:ns // .update.by @@ -367,7 +425,11 @@ obj_def_prop_trait_ns(proto_entity_writtable_reinit_ns, key_by, trait_ent_reinit // .reset.by // .reinit:writtable.reinit:ns // .reinit.by +// .pre:writtable.pre:ns +// .pre.filter:writtable.pre.filter:ns +// pre.filter.not // .dirty + const proto_entity_writtable = obj_create(proto_entity_readable); obj_def_prop_trait_with_ns( proto_entity_writtable, @@ -387,6 +449,12 @@ obj_def_prop_trait_with_ns( trait_ent_reinit, proto_entity_writtable_reinit_ns ); +obj_def_prop_trait_with_ns( + proto_entity_writtable, + key_pre, + trait_ent_pre, + proto_entity_writtable_pre_ns +); obj_def_prop_factory( proto_entity_writtable, key_dirty, diff --git a/tests/_api.test.ts b/tests/_api.test.ts index da04bfd4..8522719f 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -248,6 +248,76 @@ test('should work _value with view', () => { expect(v.dirty.val).toBe(false); }); +test('should work _value with nested view', () => { + let t; + const v = _value(5); + const w = ((t = v.view), t((_v) => _v + _v)); + const k = w.view((_v) => _v + _v); + + expect(k.val).toBe(20); + k(1); + expect(k.val).toBe(4); + expect(w.val).toBe(2); + expect(v.val).toBe(1); +}); + +test('should work _value with pre', () => { + let t; + const v = _value(5); + const w = ((t = v.pre), t((_v) => _v + _v)); + const k = w.pre((_v) => _v + 100); + + expect(w.val).toBe(5); + w(5); + expect(w.val).toBe(10); + expect(v.val).toBe(10); + expect(k.val).toBe(10); + k.val = 1; + expect(v.val).toBe(202); + expect(k.val).toBe(202); +}); + +test('should work _value with pre.filter', () => { + let t; + const v = _value(5); + const f = _value(0); + + const w = v.pre.filter((_v) => _v !== 10); + const k = ((t = w.pre.filter), (t = t.not), t(f)); + const m = k.pre.filter(); + const n = ((t = k.pre), (t = t.filter.not), t()); + + expect(w.val).toBe(5); + expect(k.val).toBe(5); + expect(m.val).toBe(5); + expect(n.val).toBe(5); + + w(10); + expect(v.val).toBe(5); + + n(0); + expect(v.val).toBe(0); + n.val = 1; + expect(v.val).toBe(0); + expect(m.dirty.val).toBe(true); + m(10); + expect(v.val).toBe(0); + m(11); + expect(v.val).toBe(11); + expect(n.val).toBe(11); + m(10); + expect(k.val).toBe(11); + m(0); + expect(m.val).toBe(11); + f(1); + k(20); + m(30); + n(0); + expect(m.val).toBe(11); + f(0); + n(0); + expect(v.val).toBe(0); +}); From e429872ae25fa76e2b33d09b5d80b65de4f6a4cb Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 17 May 2021 17:13:43 +0700 Subject: [PATCH 020/100] chore: todos updated --- src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index c8f382ca..4a3e6770 100644 --- a/src/index.ts +++ b/src/index.ts @@ -96,7 +96,13 @@ const def_prop = Object.defineProperty; TODOs: [] .flow // basic/started impl without resolve and stop signal [] value.trigger + [] value.flag + [] value.flag + [] value.flag.truthy + [] value.flag.trigger + [] value.trigger.flag [] value.from + [] value.trigger.flag.from [] signal [] v.as.value(), v.as.signal() [] v.as.readonly() From 1d2fe455c2f2d967d83143c1cedc076137b27125 Mon Sep 17 00:00:00 2001 From: betula Date: Tue, 18 May 2021 12:02:52 +0700 Subject: [PATCH 021/100] feat: add draft flow implementation to _value --- src/index.ts | 145 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 110 insertions(+), 35 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4a3e6770..19e8e040 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,7 +94,7 @@ const def_prop = Object.defineProperty; /* TODOs: - [] .flow // basic/started impl without resolve and stop signal + [] flow test cases [] value.trigger [] value.flag [] value.flag @@ -105,28 +105,45 @@ const def_prop = Object.defineProperty; [] value.trigger.flag.from [] signal [] v.as.value(), v.as.signal() - [] v.as.readonly() + [] .chan [] .combine // doubtful (use .val resolve instead) [] .join // doubtful (use .val resolve instead) - [] .chan [] ... + [] combine as root level exportable factory function + [] flow as root level exportable factory function Backlog [] .view.untrack [] .pre.untrack [] .pre.filter.untrack [] .pre.filter.not.untrack + [] v.as.readonly() + [] flow.resolve */ +// +// Global js sdk specific definitions. +// const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; + +// +// Reactive box flow section. An additional abstraction in front of basic api. +// + +const flow_stop = flow.stop; + +// +// Entity builder for value, signal and etc. +// + const pure_fn = function () {}; const pure_arrow_fn_returns_arg = (v) => v; const pure_arrow_fn_returns_not_arg = (v) => !v; @@ -154,7 +171,7 @@ const key_handler = Symbol(); const key_pre = "pre"; const key_filter = "filter"; const key_not = "not"; - +const key_flow = "flow"; const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -372,15 +389,64 @@ const trait_ent_pre_filter_not = (ctx, fn) => ( : pure_arrow_fn_returns_not_arg) ); +const generic_trait_ent_flow = (ctx, fn) => { + let started; + const f = flow(fn); + const h = [ + () => ((started || (f[0](), (started = true))), f[1]()), + ctx[key_handler][1] + ]; + return fill_entity(h, h[1] ? proto_entity_writtable : proto_entity_readable, 0, 0, 0, ctx[key_set]); +}; +const trait_ent_flow = (ctx, fn) => ( + generic_trait_ent_flow(ctx, (r, prev_value) => ( + fn(ctx[key_get](), prev_value) + )) +); +const trait_ent_flow_filter = (ctx, fn) => ( + generic_trait_ent_flow(ctx, fn + ? (fn[key_get] && (fn = fn[key_get]), + (r, prev_value) => ( + fn(ctx[key_get](), prev_value) ? ctx[key_get]() : flow_stop + )) + : () => ctx[key_get]() || flow_stop + ) +); +const trait_ent_flow_filter_not = (ctx, fn) => ( + ctx[key_flow][key_filter](fn + ? (fn[key_get] && (fn = fn[key_get]), (v) => !fn(v)) + : pure_arrow_fn_returns_not_arg) +); + + + // readable.to:ns // .to.once const proto_entity_readable_to_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); +// readable.flow.filter:ns +// .flow.filter.not +const proto_entity_writtable_flow_filter_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_flow_filter_ns, key_not, trait_ent_flow_filter_not); + +// readable.flow:ns +// .flow.filter:readable.flow.filter:ns +const proto_entity_readable_flow_ns = obj_create(pure_fn); +obj_def_prop_trait_ns_with_ns( + proto_entity_readable_flow_ns, + key_filter, + trait_ent_flow_filter, + proto_entity_writtable_flow_filter_ns +); + // readable // .sync // .to:readable.to:ns // .to.once +// .flow:readable.flow:ns +// .flow.filter:readable.flow.filter:ns +// flow.filter.not // .select // .view const proto_entity_readable = obj_create(pure_fn); @@ -391,6 +457,12 @@ obj_def_prop_trait_with_ns( trait_ent_to, proto_entity_readable_to_ns ); +obj_def_prop_trait_with_ns( + proto_entity_readable, + key_flow, + trait_ent_flow, + proto_entity_readable_flow_ns +); obj_def_prop_trait(proto_entity_readable, key_select, trait_ent_select); obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); @@ -399,23 +471,13 @@ obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); const proto_entity_writtable_update_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by); -// writtable.reset:ns -// .reset.by -const proto_entity_writtable_reset_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_reset_ns, key_by, trait_ent_reset_by); - -// writtable.reinit:ns -// .reinit.by -const proto_entity_writtable_reinit_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_reinit_ns, key_by, trait_ent_reinit_by); - // writtable.pre.filter:ns // .pre.filter.not const proto_entity_writtable_pre_filter_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_writtable_pre_filter_ns, key_not, trait_ent_pre_filter_not); // writtable.pre:ns -// .pre.filter +// .pre.filter:writtable.pre.filter:ns const proto_entity_writtable_pre_ns = obj_create(pure_fn); obj_def_prop_trait_ns_with_ns( proto_entity_writtable_pre_ns, @@ -427,15 +489,9 @@ obj_def_prop_trait_ns_with_ns( // writtable <- readable // .update:writtable.update:ns // .update.by -// .reset:writtable.reset:ns -// .reset.by -// .reinit:writtable.reinit:ns -// .reinit.by // .pre:writtable.pre:ns // .pre.filter:writtable.pre.filter:ns // pre.filter.not -// .dirty - const proto_entity_writtable = obj_create(proto_entity_readable); obj_def_prop_trait_with_ns( proto_entity_writtable, @@ -445,39 +501,58 @@ obj_def_prop_trait_with_ns( ); obj_def_prop_trait_with_ns( proto_entity_writtable, + key_pre, + trait_ent_pre, + proto_entity_writtable_pre_ns +); + +// writtable_leaf.reset:ns +// .reset.by +const proto_entity_writtable_leaf_reset_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_leaf_reset_ns, key_by, trait_ent_reset_by); + +// writtable_leaf.reinit:ns +// .reinit.by +const proto_entity_writtable_leaf_reinit_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_leaf_reinit_ns, key_by, trait_ent_reinit_by); + + +// writtable_leaf <- writtable +// .reset:writtable_leaf.reset:ns +// .reset.by +// .reinit:writtable_leaf.reinit:ns +// .reinit.by +// .dirty +const proto_entity_writtable_leaf = obj_create(proto_entity_writtable); +obj_def_prop_trait_with_ns( + proto_entity_writtable_leaf, key_reset, trait_ent_reset, - proto_entity_writtable_reset_ns + proto_entity_writtable_leaf_reset_ns ); obj_def_prop_trait_with_ns( - proto_entity_writtable, + proto_entity_writtable_leaf, key_reinit, trait_ent_reinit, - proto_entity_writtable_reinit_ns -); -obj_def_prop_trait_with_ns( - proto_entity_writtable, - key_pre, - trait_ent_pre, - proto_entity_writtable_pre_ns + proto_entity_writtable_leaf_reinit_ns ); obj_def_prop_factory( - proto_entity_writtable, + proto_entity_writtable_leaf, key_dirty, prop_factory_dirty_required_initial ); -// writtable_value <- writtable +// writtable_value <- writtable_leaf // .promise `for non trigger const proto_entity_writtable_value = obj_create( - proto_entity_writtable + proto_entity_writtable_leaf ); obj_def_prop_promise_for_non_trigger(proto_entity_writtable_value); -// writtable_value_trigger <- writtable +// writtable_value_trigger <- writtable_leaf // .promise `for trigger const proto_entity_writtable_value_trigger = obj_create( - proto_entity_writtable + proto_entity_writtable_leaf ); obj_def_prop_promise_for_trigger( proto_entity_writtable_value_trigger From 7e7499c5dea1783229f5d51dd3a341ccc2d3792e Mon Sep 17 00:00:00 2001 From: betula Date: Tue, 18 May 2021 14:03:57 +0700 Subject: [PATCH 022/100] feat: _value's flow test cases --- src/index.ts | 34 +++++++++++++-------------- tests/_api.test.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/src/index.ts b/src/index.ts index 19e8e040..f752ce36 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,7 +94,6 @@ const def_prop = Object.defineProperty; /* TODOs: - [] flow test cases [] value.trigger [] value.flag [] value.flag @@ -318,7 +317,7 @@ const trait_ent_reset = (ctx) => { const trait_ent_reset_by = (ctx, src) => { const src_get = src[key_get] ? src[key_get] : src; const e = expr(src_get, () => { - ctx[key_reset](); + trait_ent_reset(ctx); e[0]() }); e[0](); @@ -326,12 +325,12 @@ const trait_ent_reset_by = (ctx, src) => { }; const trait_ent_reinit = (ctx, initial) => { ctx[key_handler][key_initial] = initial; - ctx[key_reset](); + trait_ent_reset(ctx); }; const trait_ent_reinit_by = (ctx, src) => { const src_get = src[key_get] ? src[key_get] : src; const e = expr(src_get, () => { - ctx[key_reinit](src_get()); + trait_ent_reinit(ctx, src_get()); e[0](); }); e[0](); @@ -382,34 +381,33 @@ const trait_ent_pre_filter = (ctx, fn) => ( ) ); const trait_ent_pre_filter_not = (ctx, fn) => ( - ctx[key_pre][key_filter](fn + trait_ent_pre_filter(ctx, fn ? (fn[key_get] ? (v) => !fn[key_get](v) : (v) => !fn(v)) : pure_arrow_fn_returns_not_arg) ); -const generic_trait_ent_flow = (ctx, fn) => { - let started; - const f = flow(fn); +const trait_ent_flow = (ctx, fn) => { + let started, prev; + const f = flow(() => { + const v = ctx[key_get](); + try { return fn(v, prev) } + finally { prev = v } + }); const h = [ () => ((started || (f[0](), (started = true))), f[1]()), ctx[key_handler][1] ]; - return fill_entity(h, h[1] ? proto_entity_writtable : proto_entity_readable, 0, 0, 0, ctx[key_set]); + return fill_entity(h, h[1] ? proto_entity_writtable : proto_entity_readable, 0, 0, 0, ctx[key_set].bind()); }; -const trait_ent_flow = (ctx, fn) => ( - generic_trait_ent_flow(ctx, (r, prev_value) => ( - fn(ctx[key_get](), prev_value) - )) -); const trait_ent_flow_filter = (ctx, fn) => ( - generic_trait_ent_flow(ctx, fn + trait_ent_flow(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), - (r, prev_value) => ( - fn(ctx[key_get](), prev_value) ? ctx[key_get]() : flow_stop + (v, prev) => ( + fn(v, prev) ? v : flow_stop )) - : () => ctx[key_get]() || flow_stop + : (v) => v || flow_stop ) ); const trait_ent_flow_filter_not = (ctx, fn) => ( diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 8522719f..7c2b9526 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -319,9 +319,66 @@ test('should work _value with pre.filter', () => { expect(v.val).toBe(0); }); +test('should work _value with flow', () => { + let t; + const v = _value(5); + const x = _value(1); + const w = ((t = v.flow), t((_v) => _v + _v)); + const k = w.flow((_v) => _v + 100); + const p = k.flow((_v, _v_prev) => _v * 100 + (_v_prev || 0) * 10 + x.val); + + expect(w.val).toBe(10); + expect(k.val).toBe(110); + expect(p.val).toBe(11001); + + p.val = 6; + expect(w.val).toBe(12); + expect(k.val).toBe(112); + expect(p.val).toBe(112 * 100 + 110 * 10 + 1); + x(2); + expect(p.val).toBe(112 * 100 + 112 * 10 + 2); +}); + +test('should work _value with flow.filter', () => { + let t; + const v = _value(5); + const f = _value(0); + const w = v.flow.filter((_v) => _v !== 10); + const k = ((t = w.flow.filter), (t = t.not), t(f)); + const m = k.flow.filter(); + const n = ((t = k.flow), (t = t.filter.not), t()); + expect(w.val).toBe(5); + expect(k.val).toBe(5); + expect(m.val).toBe(5); + expect(n.val).toBe(void 0); + + m(0); + expect(w.val).toBe(0); + expect(k.val).toBe(0); + expect(m.val).toBe(5); + expect(n.val).toBe(0); + + f(1); + w(8); + expect(v.val).toBe(8); + expect(w.val).toBe(8); + expect(k.val).toBe(0); + expect(m.val).toBe(5); + + f(); + expect(k.val).toBe(8); + expect(m.val).toBe(8); + + n(10); + expect(v.val).toBe(10); + expect(w.val).toBe(8); + + v(11); + expect(w.val).toBe(11); +}); From 97d26dec83cabd37cdedd470cb23371bddf7e59b Mon Sep 17 00:00:00 2001 From: betula Date: Tue, 18 May 2021 14:44:56 +0700 Subject: [PATCH 023/100] feat: added test case for _value's non writtable flow --- src/index.ts | 16 ++++++++++------ tests/_api.test.ts | 31 ++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index f752ce36..faecb7f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,9 +104,9 @@ const def_prop = Object.defineProperty; [] value.trigger.flag.from [] signal [] v.as.value(), v.as.signal() - [] .chan - [] .combine // doubtful (use .val resolve instead) - [] .join // doubtful (use .val resolve instead) + [] x.combine([a,b,c]) -> [x,a,b,c] + [] x.select.multiple({a:fn, b:fn}).op((ctx)=> {ctx.a.to(m); ctx.b.to(p)}) + [] x.op [] ... [] combine as root level exportable factory function [] flow as root level exportable factory function @@ -118,6 +118,8 @@ const def_prop = Object.defineProperty; [] .pre.filter.not.untrack [] v.as.readonly() [] flow.resolve + [] .chan + [] .combine */ @@ -397,9 +399,11 @@ const trait_ent_flow = (ctx, fn) => { }); const h = [ () => ((started || (f[0](), (started = true))), f[1]()), - ctx[key_handler][1] + ctx[key_set] && ctx[key_set].bind() ]; - return fill_entity(h, h[1] ? proto_entity_writtable : proto_entity_readable, 0, 0, 0, ctx[key_set].bind()); + return fill_entity(h, + h[1] ? proto_entity_writtable : proto_entity_readable + ); }; const trait_ent_flow_filter = (ctx, fn) => ( trait_ent_flow(ctx, fn @@ -411,7 +415,7 @@ const trait_ent_flow_filter = (ctx, fn) => ( ) ); const trait_ent_flow_filter_not = (ctx, fn) => ( - ctx[key_flow][key_filter](fn + trait_ent_flow_filter(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), (v) => !fn(v)) : pure_arrow_fn_returns_not_arg) ); diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 7c2b9526..b2820338 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -172,7 +172,7 @@ test('should work _value with val', () => { expect(v.dirty.val).toBe(false); }); -test('should work _value with to', () => { +test('should work _value with to, to.once', () => { const spy_to = jest.fn(); const spy_to_once = jest.fn(); const v = _value(0); @@ -198,12 +198,13 @@ test('should work _value with to', () => { test('should work _value with select', () => { const spy = jest.fn(); - let t; + let t, s; const v = _value(5); const k = _value(0); - (t = v.select); t((_v) => Math.abs(_v - k.val)).sync(spy); + (t = v.select); s = t((_v) => Math.abs(_v - k.val)).sync(spy); + expect(s.val).toBe(5); expect(spy).toHaveBeenCalledWith(5, void 0); expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); k(10); @@ -216,6 +217,7 @@ test('should work _value with select', () => { v(5); expect(spy).toHaveBeenCalledWith(6, 4); expect(spy).toHaveBeenCalledTimes(1); spy.mockReset(); + expect(s.get()).toBe(6); }); test('should work _value with view', () => { @@ -277,7 +279,7 @@ test('should work _value with pre', () => { expect(k.val).toBe(202); }); -test('should work _value with pre.filter', () => { +test('should work _value with pre.filter, pre.filter.not', () => { let t; const v = _value(5); const f = _value(0); @@ -340,7 +342,7 @@ test('should work _value with flow', () => { }); -test('should work _value with flow.filter', () => { +test('should work _value with flow.filter, flow.filter.not', () => { let t; const v = _value(5); const f = _value(0); @@ -350,6 +352,10 @@ test('should work _value with flow.filter', () => { const m = k.flow.filter(); const n = ((t = k.flow), (t = t.filter.not), t()); + expect(n.dirty).toBeUndefined(); + expect(m.reset).toBeUndefined(); + expect(k.reinit).toBeUndefined(); + expect(w.val).toBe(5); expect(k.val).toBe(5); expect(m.val).toBe(5); @@ -380,6 +386,21 @@ test('should work _value with flow.filter', () => { expect(w.val).toBe(11); }); +test('should work _value with readable flow', () => { + const v = _value("Hi"); + const f = v.select((_v) => _v[1]).flow((v) => v + v); + + expect(typeof f).toBe("object"); + expect(f.set).toBeUndefined(); + expect(f.update).toBeUndefined(); + expect(f.reset).toBeUndefined(); + expect(f.reinit).toBeUndefined(); + expect(f.dirty).toBeUndefined(); + expect(f.get()).toBe('ii'); + expect(f.val).toBe('ii'); + v("/+"); + expect(f.val).toBe('++'); +}); From 946be5c1c24334c7a10c58bde267b89e4c0011af Mon Sep 17 00:00:00 2001 From: betula Date: Tue, 18 May 2021 18:51:17 +0700 Subject: [PATCH 024/100] feat: _value's promise implementation updated --- src/index.ts | 78 +++++++++++++++++++++------------------------- tests/_api.test.ts | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 42 deletions(-) diff --git a/src/index.ts b/src/index.ts index faecb7f9..de5d4095 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,16 +94,18 @@ const def_prop = Object.defineProperty; /* TODOs: + [] think about trigger implementation + [] add "is_touched" flag to "[key_handler]" necessary for disallow setting for trigger (until reset), + and for .touched implementation (same as dirty) + [] value.trigger - [] value.flag - [] value.flag - [] value.flag.truthy - [] value.flag.trigger - [] value.trigger.flag + [] value.trigger.flag // (false -> true) + [] value.trigger.flag.invert // (true -> false) [] value.from [] value.trigger.flag.from [] signal [] v.as.value(), v.as.signal() + [] add signal support to "flow" [] x.combine([a,b,c]) -> [x,a,b,c] [] x.select.multiple({a:fn, b:fn}).op((ctx)=> {ctx.a.to(m); ctx.b.to(p)}) [] x.op @@ -133,6 +135,7 @@ const def_prop = Object.defineProperty; const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; +const new_symbol = Symbol; // @@ -153,13 +156,13 @@ const key_proto = "__proto__"; const key_get = "get"; const key_set = "set"; const key_promise = "promise"; -const key_promise_internal = Symbol(); +const key_promise_internal = new_symbol(); const key_reset = "reset"; -const key_initial = Symbol(); -const key_dirty_handler = Symbol(); +const key_initial = new_symbol(); +const key_dirty_handler = new_symbol(); const key_dirty = "dirty"; const key_sync = "sync"; -const key_ctx = Symbol(); +const key_ctx = new_symbol(); const key_by = "by"; const key_reinit = "reinit"; const key_update = "update"; @@ -168,11 +171,12 @@ const key_once = "once"; const key_to = "to"; const key_select = "select"; const key_view = "view"; -const key_handler = Symbol(); +const key_handler = new_symbol(); const key_pre = "pre"; const key_filter = "filter"; const key_not = "not"; const key_flow = "flow"; +const key_is_trigger = new_symbol(); const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -220,7 +224,7 @@ const obj_def_prop_factory = (obj, key, factory) => } }); -const obj_def_prop_promise_for_non_trigger = (obj) => { +const obj_def_prop_promise = (obj) => { return obj_def_prop(obj, key_promise, { get() { const ctx = this; @@ -228,7 +232,7 @@ const obj_def_prop_promise_for_non_trigger = (obj) => { ctx[key_promise_internal] = new Promise((resolve) => // TODO: should be the highest priority. expr(ctx[key_get], () => { - ctx[key_promise_internal] = 0; + if (!ctx[key_handler][key_is_trigger]) ctx[key_promise_internal] = 0; resolve(ctx[key_get]()); })[0]() ); @@ -238,24 +242,12 @@ const obj_def_prop_promise_for_non_trigger = (obj) => { }); }; -const obj_def_prop_promise_for_trigger = (obj) => { - return obj_def_prop(obj, key_promise, { - get() { - const ctx = this; - if (!ctx[key_promise_internal]) { - ctx[key_promise_internal] = new Promise((resolve) => - expr(ctx[key_get], () => resolve(ctx[key_get]()))[0]() - ); - } - return ctx[key_promise_internal]; - } - }); -}; -const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { +const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?, is_trigger?) => { const set = _set || handler[1]; const get = _get || handler[0]; has_initial && (handler[key_initial] = initial); + is_trigger && (handler[key_is_trigger] = is_trigger); let ctx; if (set) { @@ -451,6 +443,7 @@ obj_def_prop_trait_ns_with_ns( // flow.filter.not // .select // .view +// .promise const proto_entity_readable = obj_create(pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); obj_def_prop_trait_with_ns( @@ -467,6 +460,7 @@ obj_def_prop_trait_with_ns( ); obj_def_prop_trait(proto_entity_readable, key_select, trait_ent_select); obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); +obj_def_prop_promise(proto_entity_readable); // writtable.update:ns // .update.by @@ -544,30 +538,30 @@ obj_def_prop_factory( prop_factory_dirty_required_initial ); -// writtable_value <- writtable_leaf -// .promise `for non trigger -const proto_entity_writtable_value = obj_create( - proto_entity_writtable_leaf -); -obj_def_prop_promise_for_non_trigger(proto_entity_writtable_value); +// // writtable_value <- writtable_leaf +// // .promise `for non trigger +// const proto_entity_writtable_value = obj_create( +// proto_entity_writtable_leaf +// ); +// obj_def_prop_promise(proto_entity_writtable_value); -// writtable_value_trigger <- writtable_leaf -// .promise `for trigger -const proto_entity_writtable_value_trigger = obj_create( - proto_entity_writtable_leaf -); -obj_def_prop_promise_for_trigger( - proto_entity_writtable_value_trigger -); +// // writtable_value_trigger <- writtable_leaf +// // .promise `for trigger +// const proto_entity_writtable_value_trigger = obj_create( +// proto_entity_writtable_leaf +// ); +// obj_def_prop_promise_for_trigger( +// proto_entity_writtable_value_trigger +// ); export const _value = (initial) => ( - fill_entity(box(initial), proto_entity_writtable_value, 1, initial) + fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) ); export const _value_trigger = (initial) => ( - fill_entity(box(initial), proto_entity_writtable_value_trigger, 1, initial) + fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial, 0, 0, 1) ) diff --git a/tests/_api.test.ts b/tests/_api.test.ts index b2820338..055acb84 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -402,6 +402,67 @@ test('should work _value with readable flow', () => { expect(f.val).toBe('++'); }); +test('should work _value with promise for value, select, view, pre, flow, readable flow', async () => { + const v = _value(0); + const s = v.select(t => t + 1); + const w = v.view(t => t + 2); + const p = v.pre(t => t - 1); + const f = v.flow(t => t + 5); + const r = s.flow(t => t + 3); + + expect(v.promise).toBe(v.promise); + expect(v.promise).not.toBe(s.promise); + expect(s.promise).toBe(s.promise); + expect(v.promise).not.toBe(w.promise); + expect(w.promise).toBe(w.promise); + expect(v.promise).not.toBe(p.promise); + expect(p.promise).toBe(p.promise); + expect(v.promise).not.toBe(f.promise); + expect(f.promise).toBe(f.promise); + expect(v.promise).not.toBe(r.promise); + expect(r.promise).toBe(r.promise); + + const v_p = v.promise; + + setTimeout(() => v.update((k) => k + 10), 3); + const a1 = await Promise.all([ + v.promise, + s.promise, + w.promise, + p.promise, + f.promise, + r.promise + ]); + expect(a1).toStrictEqual([ + 10, + 11, + 12, + 10, + 15, + 14 + ]); + + expect(v_p).not.toBe(v.promise); + expect(v.promise).toBe(v.promise); + + setTimeout(() => v.update((k) => k + 10), 3); + const a2 = await Promise.all([ + v.promise, + s.promise, + w.promise, + p.promise, + f.promise, + r.promise + ]); + expect(a2).toStrictEqual([ + 20, + 21, + 22, + 20, + 25, + 24 + ]); +}); From 76f7a956b5d0d1254226fa626786f9b8fa4b298b Mon Sep 17 00:00:00 2001 From: betula Date: Tue, 18 May 2021 19:10:38 +0700 Subject: [PATCH 025/100] chore: todos updated --- src/index.ts | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/index.ts b/src/index.ts index de5d4095..f5061d09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,8 +95,9 @@ const def_prop = Object.defineProperty; /* TODOs: [] think about trigger implementation - [] add "is_touched" flag to "[key_handler]" necessary for disallow setting for trigger (until reset), - and for .touched implementation (same as dirty) + [] add "key_trigger_touched" flag to "[key_handler]" + necessary for disallow setting for trigger (until reset), + (explained in todo below) [] value.trigger [] value.trigger.flag // (false -> true) @@ -249,6 +250,9 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?, is_tr has_initial && (handler[key_initial] = initial); is_trigger && (handler[key_is_trigger] = is_trigger); + // TODO: if is trigger should be create decorated "set" function, who set [key_trigger_touched] to 1 + // and will disable "set" if it already setted to 1 + let ctx; if (set) { ctx = set; @@ -538,22 +542,6 @@ obj_def_prop_factory( prop_factory_dirty_required_initial ); -// // writtable_value <- writtable_leaf -// // .promise `for non trigger -// const proto_entity_writtable_value = obj_create( -// proto_entity_writtable_leaf -// ); -// obj_def_prop_promise(proto_entity_writtable_value); - -// // writtable_value_trigger <- writtable_leaf -// // .promise `for trigger -// const proto_entity_writtable_value_trigger = obj_create( -// proto_entity_writtable_leaf -// ); -// obj_def_prop_promise_for_trigger( -// proto_entity_writtable_value_trigger -// ); - export const _value = (initial) => ( From b861eca180d9f5d4fb7f4c08998836ffb3cc2637 Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 06:45:24 +0700 Subject: [PATCH 026/100] feat: public factory _value.trigger added --- src/index.ts | 65 ++++++++++++++++++++++++++++------------------ tests/_api.test.ts | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/index.ts b/src/index.ts index f5061d09..5e3807ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ import React, { Context, FC } from 'react'; import { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; export { + _value, + value, selector, prop, @@ -94,12 +96,6 @@ const def_prop = Object.defineProperty; /* TODOs: - [] think about trigger implementation - [] add "key_trigger_touched" flag to "[key_handler]" - necessary for disallow setting for trigger (until reset), - (explained in todo below) - - [] value.trigger [] value.trigger.flag // (false -> true) [] value.trigger.flag.invert // (true -> false) [] value.from @@ -108,8 +104,12 @@ const def_prop = Object.defineProperty; [] v.as.value(), v.as.signal() [] add signal support to "flow" [] x.combine([a,b,c]) -> [x,a,b,c] - [] x.select.multiple({a:fn, b:fn}).op((ctx)=> {ctx.a.to(m); ctx.b.to(p)}) - [] x.op + [] x.select.multiple({a:fn, b:fn}).group((ctx)=> {ctx.a.to(m); ctx.b.to(p)}).group() + [] x.group -- x.op -- x.block + x.block((ctx) => ({ // if returns non undefined + a: ctx.a, + b: ctx.a.select() + })).b.val [] ... [] combine as root level exportable factory function [] flow as root level exportable factory function @@ -138,15 +138,23 @@ const obj_def_prop = Object.defineProperty; const obj_create = Object.create; const new_symbol = Symbol; - // -// Reactive box flow section. An additional abstraction in front of basic api. +// Reactive box specific definitions. // const flow_stop = flow.stop; // -// Entity builder for value, signal and etc. +// Entity builder for value, signal and etc. Typings. +// + +type _Value = { + (initial?: any): any; + trigger: (initial?: any) => any; +} + +// +// Entity builder implementation. // const pure_fn = function () {}; @@ -177,7 +185,11 @@ const key_pre = "pre"; const key_filter = "filter"; const key_not = "not"; const key_flow = "flow"; -const key_is_trigger = new_symbol(); +const key_reset_promise_by_reset = new_symbol(); +const key_touched_internal = new_symbol(); +const key_trigger = "trigger"; + + const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -233,7 +245,7 @@ const obj_def_prop_promise = (obj) => { ctx[key_promise_internal] = new Promise((resolve) => // TODO: should be the highest priority. expr(ctx[key_get], () => { - if (!ctx[key_handler][key_is_trigger]) ctx[key_promise_internal] = 0; + if (!ctx[key_handler][key_reset_promise_by_reset]) ctx[key_promise_internal] = 0; resolve(ctx[key_get]()); })[0]() ); @@ -244,14 +256,10 @@ const obj_def_prop_promise = (obj) => { }; -const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?, is_trigger?) => { - const set = _set || handler[1]; - const get = _get || handler[0]; +const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { + let set = _set || handler[1]; + let get = _get || handler[0]; has_initial && (handler[key_initial] = initial); - is_trigger && (handler[key_is_trigger] = is_trigger); - - // TODO: if is trigger should be create decorated "set" function, who set [key_trigger_touched] to 1 - // and will disable "set" if it already setted to 1 let ctx; if (set) { @@ -311,6 +319,7 @@ const trait_ent_sync = (ctx, fn) => { const trait_ent_reset = (ctx) => { ctx[key_promise_internal] = 0; ctx[key_handler][1](ctx[key_handler][key_initial]); + ctx[key_handler][key_touched_internal] = 0; }; const trait_ent_reset_by = (ctx, src) => { const src_get = src[key_get] ? src[key_get] : src; @@ -387,6 +396,7 @@ const trait_ent_pre_filter_not = (ctx, fn) => ( ); const trait_ent_flow = (ctx, fn) => { + fn || (fn = pure_arrow_fn_returns_arg); let started, prev; const f = flow(() => { const v = ctx[key_get](); @@ -543,14 +553,19 @@ obj_def_prop_factory( ); +const value_trigger = (initial) => { + const handler = box(initial, () => (handler[key_touched_internal] = 1)); + handler[key_reset_promise_by_reset] = 1; + return fill_entity(handler, proto_entity_writtable_leaf, 1, initial, 0, + (v) => { handler[key_touched_internal] || handler[1](v) } + ); +} -export const _value = (initial) => ( +const _value = ((initial) => ( fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) -); +)) as _Value; -export const _value_trigger = (initial) => ( - fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial, 0, 0, 1) -) +_value[key_trigger] = value_trigger; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 055acb84..68da91af 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -464,7 +464,64 @@ test('should work _value with promise for value, select, view, pre, flow, readab ]); }); +test('should work _value.trigger common support and promise', async () => { + let t; + const v = _value.trigger(0); + + let promise = v.promise; + expect(v.promise).toBe(promise); + + setTimeout(v.bind(0, 1)); + expect(await v.promise).toBe(1); + expect(v.promise).toBe(promise); + expect(v.val).toBe(1); + + v.val = 2; + expect(v.promise).toBe(promise); + expect(v.val).toBe(1); + + v.reset(); + expect(v.promise).not.toBe(promise); + promise = v.promise; + expect(v.val).toBe(0); + setTimeout(v.bind(0, 5)); + expect(await v.promise).toBe(5); + expect(v.val).toBe(5); + + v(7); + expect(v.promise).toBe(promise); + expect(v.val).toBe(5); + v.reinit(10); + expect(v.get()).toBe(10); + expect(v.promise).not.toBe(promise); + promise = v.promise; + v.update(v => v + 5); + expect(((t = v.get), t())).toBe(15); + expect(v.promise).toBe(promise); +}); +test('should work _value.trigger with select, update.by, flow, pre, view', () => { + const p = _value(''); + const v = _value.trigger('e'); + const t = v.pre((k) => 'h' + k).view((k) => k + 'lo').update.by(p); + const s = t.select((v) => v.slice(0, 3)); + const f = t.flow((v) => v.slice(-3) + p.val); + + expect(s.val).toBe('elo'); + expect(f.val).toBe('elo'); + p.update(() => 'el'); + expect(t.val).toBe('hello'); + expect(s.val).toBe('hel'); + expect(f.val).toBe('lloel'); + p('x'); + expect(t.val).toBe('hello'); + expect(s.val).toBe('hel'); + expect(f.val).toBe('llox'); + + t.reset(); + expect(s.val).toBe('elo'); + expect(f.val).toBe('elox'); +}); From 68b6c8a4275fcdacda25ca7f8fcdb390659eccd2 Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 07:30:38 +0700 Subject: [PATCH 027/100] feat: trigger.flag and trigger.flag.invert added to _value --- src/index.ts | 41 +++++++++++++++++++++++++++++++---------- tests/_api.test.ts | 20 ++++++++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5e3807ba..8eb4dcb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -96,9 +96,8 @@ const def_prop = Object.defineProperty; /* TODOs: - [] value.trigger.flag // (false -> true) - [] value.trigger.flag.invert // (true -> false) - [] value.from + [] value.from -- synonym with selector but with different types + [] selector [] value.trigger.flag.from [] signal [] v.as.value(), v.as.signal() @@ -137,6 +136,8 @@ const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; const new_symbol = Symbol; +const const_true = true; +const const_false = false; // // Reactive box specific definitions. @@ -150,7 +151,15 @@ const flow_stop = flow.stop; type _Value = { (initial?: any): any; - trigger: (initial?: any) => any; + trigger: { + (initial?: any): any; + flag: { + (): any; + invert: { + (): any; + } + } + } } // @@ -188,6 +197,8 @@ const key_flow = "flow"; const key_reset_promise_by_reset = new_symbol(); const key_touched_internal = new_symbol(); const key_trigger = "trigger"; +const key_flag = "flag"; +const key_invert = "invert"; @@ -404,7 +415,7 @@ const trait_ent_flow = (ctx, fn) => { finally { prev = v } }); const h = [ - () => ((started || (f[0](), (started = true))), f[1]()), + () => ((started || (f[0](), (started = 1))), f[1]()), ctx[key_set] && ctx[key_set].bind() ]; return fill_entity(h, @@ -553,19 +564,29 @@ obj_def_prop_factory( ); -const value_trigger = (initial) => { + +const make_trigger = (initial, has_to?, to?) => { const handler = box(initial, () => (handler[key_touched_internal] = 1)); + const set = has_to + ? () => { handler[key_touched_internal] || handler[1](to) } + : (v) => { handler[key_touched_internal] || handler[1](v) }; handler[key_reset_promise_by_reset] = 1; - return fill_entity(handler, proto_entity_writtable_leaf, 1, initial, 0, - (v) => { handler[key_touched_internal] || handler[1](v) } - ); + return fill_entity(handler, proto_entity_writtable_leaf, 1, initial, 0, set); } +const value_trigger = (initial) => make_trigger(initial); +const value_trigger_flag = () => make_trigger(const_false, 1, const_true); +const value_trigger_flag_invert = () => make_trigger(const_true, 1, const_false); + + + const _value = ((initial) => ( fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) )) as _Value; -_value[key_trigger] = value_trigger; +value_trigger_flag[key_invert] = value_trigger_flag_invert; +value_trigger[key_flag] = value_trigger_flag; +_value[key_trigger] = value_trigger as any; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 68da91af..a89601a9 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -524,4 +524,24 @@ test('should work _value.trigger with select, update.by, flow, pre, view', () => expect(f.val).toBe('elox'); }); +test('should work _value.trigger.flag and .trigger.flag.invert', () => { + const f = _value.trigger.flag(); + const i = _value.trigger.flag.invert(); + + expect(f.val).toBe(false); + expect(i.val).toBe(true); + f(); i(); + expect(f.val).toBe(true); + expect(i.val).toBe(false); + f(); i(); + expect(f.val).toBe(true); + expect(i.val).toBe(false); + f.reset(); i.reset(); + expect(f.val).toBe(false); + expect(i.val).toBe(true); + f(); i(); + expect(f.val).toBe(true); + expect(i.val).toBe(false); +}); + From 8005a6fc2c2f0dab00934b15d1ee83971ff3818c Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 07:51:31 +0700 Subject: [PATCH 028/100] feat: _selector and _value.from draft implementation added --- src/index.ts | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8eb4dcb4..72e0ff5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; export { _value, + _selector, value, selector, @@ -96,11 +97,9 @@ const def_prop = Object.defineProperty; /* TODOs: - [] value.from -- synonym with selector but with different types - [] selector - [] value.trigger.flag.from + [] test cases for selector and value.from [] signal - [] v.as.value(), v.as.signal() + [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] add signal support to "flow" [] x.combine([a,b,c]) -> [x,a,b,c] [] x.select.multiple({a:fn, b:fn}).group((ctx)=> {ctx.a.to(m); ctx.b.to(p)}).group() @@ -122,6 +121,7 @@ const def_prop = Object.defineProperty; [] flow.resolve [] .chan [] .combine + [] value.trigger.flag.from */ @@ -136,8 +136,6 @@ const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; const new_symbol = Symbol; -const const_true = true; -const const_false = false; // // Reactive box specific definitions. @@ -149,18 +147,25 @@ const flow_stop = flow.stop; // Entity builder for value, signal and etc. Typings. // -type _Value = { +type ValueFactory = { (initial?: any): any; trigger: { (initial?: any): any; flag: { - (): any; + (initial?: any): any; invert: { - (): any; + (initial?: any): any; } } + }; + from: { + (fn: () => any): any; } } +type SelectorFactory = { + (fn: () => any): any; +} + // // Entity builder implementation. @@ -199,6 +204,7 @@ const key_touched_internal = new_symbol(); const key_trigger = "trigger"; const key_flag = "flag"; const key_invert = "invert"; +const key_from = "from"; @@ -565,30 +571,37 @@ obj_def_prop_factory( -const make_trigger = (initial, has_to?, to?) => { +const make_trigger = (initial, has_to?) => { const handler = box(initial, () => (handler[key_touched_internal] = 1)); const set = has_to - ? () => { handler[key_touched_internal] || handler[1](to) } + ? () => { handler[key_touched_internal] || handler[1](!handler[0]()) } : (v) => { handler[key_touched_internal] || handler[1](v) }; handler[key_reset_promise_by_reset] = 1; return fill_entity(handler, proto_entity_writtable_leaf, 1, initial, 0, set); } + + const value_trigger = (initial) => make_trigger(initial); -const value_trigger_flag = () => make_trigger(const_false, 1, const_true); -const value_trigger_flag_invert = () => make_trigger(const_true, 1, const_false); +const value_trigger_flag = (initial) => make_trigger(!!initial, 1); +const value_trigger_flag_invert = (initial) => make_trigger(!initial, 1); + +const _selector: SelectorFactory = (fn) => ( + fill_entity(sel(fn).slice(0, 1), proto_entity_readable) +) -const _value = ((initial) => ( +const _value: ValueFactory = ((initial) => ( fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) -)) as _Value; +)) as any; + + value_trigger_flag[key_invert] = value_trigger_flag_invert; value_trigger[key_flag] = value_trigger_flag; _value[key_trigger] = value_trigger as any; - - +_value[key_from] = _selector; From fa2b6f159fdf24c15196e1e414a2a5dac9ea58e0 Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 07:55:11 +0700 Subject: [PATCH 029/100] chore: todos updated --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 72e0ff5b..aee4945f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -98,6 +98,7 @@ const def_prop = Object.defineProperty; /* TODOs: [] test cases for selector and value.from + [] value.from(get, set) <- add the "set" as a second (optional) argument [] signal [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] add signal support to "flow" From eec39a4c84b0fe7ccd409b600abba770fa05d1b6 Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 16:58:51 +0700 Subject: [PATCH 030/100] feat: _selector and _value.from added --- src/index.ts | 29 ++++++--- tests/_api.test.ts | 145 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 164 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index aee4945f..87ba5bcf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; export { _value, _selector, + _transaction, value, selector, @@ -97,13 +98,11 @@ const def_prop = Object.defineProperty; /* TODOs: - [] test cases for selector and value.from - [] value.from(get, set) <- add the "set" as a second (optional) argument - [] signal + [] signal <- purpose to implement signal through box compare function [] v.as.value(), v.as.signal(), v.as.trigger.flag() - [] add signal support to "flow" [] x.combine([a,b,c]) -> [x,a,b,c] [] x.select.multiple({a:fn, b:fn}).group((ctx)=> {ctx.a.to(m); ctx.b.to(p)}).group() + [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] x.group -- x.op -- x.block x.block((ctx) => ({ // if returns non undefined a: ctx.a, @@ -123,6 +122,7 @@ const def_prop = Object.defineProperty; [] .chan [] .combine [] value.trigger.flag.from + [] add signal support to "flow" -- on thinking */ @@ -160,7 +160,7 @@ type ValueFactory = { } }; from: { - (fn: () => any): any; + (get: () => any, set?: (v) => any): any; } } type SelectorFactory = { @@ -383,7 +383,7 @@ const trait_ent_view = (ctx, fn) => ( fill_entity(ctx[key_handler], ctx[key_proto], 0, 0, fn ? () => fn(ctx[key_get]()) : ctx[key_get], - ctx[key_set].bind() + ctx[key_set] && ctx[key_set].bind() ) ); const trait_ent_pre = (ctx, fn) => ( @@ -586,8 +586,10 @@ const make_trigger = (initial, has_to?) => { const value_trigger = (initial) => make_trigger(initial); const value_trigger_flag = (initial) => make_trigger(!!initial, 1); const value_trigger_flag_invert = (initial) => make_trigger(!initial, 1); - - +const value_from = (get, set?) => ( + (get = sel(get).slice(0, 1), set && (get = get.concat(set))), + fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) +) const _selector: SelectorFactory = (fn) => ( fill_entity(sel(fn).slice(0, 1), proto_entity_readable) @@ -602,9 +604,18 @@ const _value: ValueFactory = ((initial) => ( value_trigger_flag[key_invert] = value_trigger_flag_invert; value_trigger[key_flag] = value_trigger_flag; _value[key_trigger] = value_trigger as any; -_value[key_from] = _selector; +_value[key_from] = value_from; +// +// Reactive box functions with additional abstraction +// + +const _transaction = (fn) => { + const finish = transaction(); + try { return fn() } + finally { finish() } +}; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index a89601a9..dbc2239e 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1,4 +1,4 @@ -import { _value } from '../src'; +import { _value, _selector, _transaction } from '../src'; test('should work _value with call, get, set, update, sync', () => { const spy = jest.fn(); @@ -544,4 +544,147 @@ test('should work _value.trigger.flag and .trigger.flag.invert', () => { expect(i.val).toBe(false); }); +test('should work _selector basic support', () => { + let t; + const spy = jest.fn(); + const a = _value(0); + const b = _value(1); + const s = _selector(() => a.val * 100 + b.val); + + expect(typeof s).toBe('object'); + expect(s.to).not.toBeUndefined(); + expect(s.to.once).not.toBeUndefined(); + expect(s.flow).not.toBeUndefined(); + expect(s.flow.filter).not.toBeUndefined(); + expect(s.flow.filter.not).not.toBeUndefined(); + expect(s.view).not.toBeUndefined(); + expect(s.promise).not.toBeUndefined(); + + expect(s.set).toBeUndefined(); + expect(s.update).toBeUndefined(); + expect(s.pre).toBeUndefined(); + expect(s.reset).toBeUndefined(); + expect(s.reinit).toBeUndefined(); + expect(s.dirty).toBeUndefined(); + + expect(s.get()).toBe(s.val); + expect(s.val).toBe(1); + + (t = s.sync), t(spy); + expect(spy).toBeCalledWith(1, void 0); spy.mockReset(); + + a.val = 1; + expect(spy).toBeCalledWith(101, 1); spy.mockReset(); + b.val = 2; + expect(spy).toBeCalledWith(102, 101); spy.mockReset(); + + _transaction(() => { + a.val = 0; + b.val = 102; + }); + expect(spy).toBeCalledTimes(0); + expect(a.val).toBe(0); + expect(b.val).toBe(102); +}); + +test('should work _selector with to, flow, view', () => { + let t; + const spy = jest.fn(); + const b = _value(1); + const v = _value(0); + const s = _selector(() => v.val + 1); + const f = s.flow.filter.not(b); + const w = s.view((v) => v + 5); + + (t = s.to), t(spy); + expect(spy).toBeCalledTimes(0); + + v(1); + expect(spy).toBeCalledWith(2, 1); spy.mockReset(); + expect(s.val).toBe(2); + expect(f.val).toBe(void 0); + expect(w.val).toBe(7); + v(2); + expect(spy).toBeCalledWith(3, 2); spy.mockReset(); + expect(s.val).toBe(3); + expect(f.val).toBe(void 0); + expect(w.val).toBe(8); + b(0); + expect(f.val).toBe(3); +}); + +test('should work _value.from with one argument', () => { + let t; + const spy = jest.fn(); + const a = _value(0); + const v = _value.from(() => a.val + 1); + expect(v.val).toBe(1); + (t = v.to), t(spy); + + a.val = 1; + expect(v.val).toBe(2); + expect(spy).toBeCalledWith(2, 1); + + expect(typeof v).toBe('object'); + expect(v.to.once).not.toBeUndefined(); + expect(v.flow).not.toBeUndefined(); + expect(v.flow.filter).not.toBeUndefined(); + expect(v.flow.filter.not).not.toBeUndefined(); + expect(v.view).not.toBeUndefined(); + expect(v.promise).not.toBeUndefined(); + + expect(v.set).toBeUndefined(); + expect(v.update).toBeUndefined(); + expect(v.pre).toBeUndefined(); + expect(v.reset).toBeUndefined(); + expect(v.reinit).toBeUndefined(); + expect(v.dirty).toBeUndefined(); +}); + +test('should work _value.from with two arguments', () => { + let t; + const spy = jest.fn(); + const u = _value(0); + const a = _value(0); + const v = _value.from(() => a.val + 1, (v) => a(v + v)); + expect(v.val).toBe(1); + (t = v.to), (t = t(spy)); + (t = t.update), (t = t.by), t(() => u.val); + + a.val = 1; + expect(v.val).toBe(2); + expect(spy).toBeCalledWith(2, 1); spy.mockReset(); + + expect(typeof v).toBe('function'); + expect(v.sync).not.toBeUndefined(); + expect(v.to.once).not.toBeUndefined(); + expect(v.flow).not.toBeUndefined(); + expect(v.flow.filter).not.toBeUndefined(); + expect(v.flow.filter.not).not.toBeUndefined(); + expect(v.view).not.toBeUndefined(); + expect(v.set).not.toBeUndefined(); + expect(v.update).not.toBeUndefined(); + expect(v.update.by).not.toBeUndefined(); + expect(v.pre).not.toBeUndefined(); + expect(v.pre.filter).not.toBeUndefined(); + expect(v.pre.filter.not).not.toBeUndefined(); + expect(v.promise).not.toBeUndefined(); + + expect(v.reset).toBeUndefined(); + expect(v.reinit).toBeUndefined(); + expect(v.dirty).toBeUndefined(); + + u(1); + expect(v.val).toBe(3); + expect(spy).toBeCalledWith(3, 2); spy.mockReset(); +}); + + + + + + + + + From 24b05ba97591bfcb39d7eff50d837146ff0339ea Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 17:12:31 +0700 Subject: [PATCH 031/100] chore: todos updated --- src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 87ba5bcf..a5f7a74d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -99,6 +99,11 @@ const def_prop = Object.defineProperty; /* TODOs: [] signal <- purpose to implement signal through box compare function + [] add barrier support to _value, and _signal + s.barrier -> synonym to .flow.filter.untrack for values, **but different for signals** + s.barrier.not -> synonym to .flow.filter.not.untrack for values, but different for signals + ... hmm.. on thinking + [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] x.combine([a,b,c]) -> [x,a,b,c] [] x.select.multiple({a:fn, b:fn}).group((ctx)=> {ctx.a.to(m); ctx.b.to(p)}).group() @@ -122,7 +127,6 @@ const def_prop = Object.defineProperty; [] .chan [] .combine [] value.trigger.flag.from - [] add signal support to "flow" -- on thinking */ From 8cc1f951fe12557eda123178978b41869f4bf549 Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 17:20:33 +0700 Subject: [PATCH 032/100] chore: todos updated --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index a5f7a74d..7b540b96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -103,6 +103,8 @@ const def_prop = Object.defineProperty; s.barrier -> synonym to .flow.filter.untrack for values, **but different for signals** s.barrier.not -> synonym to .flow.filter.not.untrack for values, but different for signals ... hmm.. on thinking + ... + Is "s.filter" instead of "s.flow.filter" or should be both? [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] x.combine([a,b,c]) -> [x,a,b,c] @@ -127,6 +129,7 @@ const def_prop = Object.defineProperty; [] .chan [] .combine [] value.trigger.flag.from + [] .map <- sysnonym for .view (on thinking) */ From 0a5c457231b57cffa0cc1d42a15e8094dd7e95d2 Mon Sep 17 00:00:00 2001 From: betula Date: Wed, 19 May 2021 17:57:11 +0700 Subject: [PATCH 033/100] chore: todos updated --- src/index.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7b540b96..f44793de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -99,17 +99,22 @@ const def_prop = Object.defineProperty; /* TODOs: [] signal <- purpose to implement signal through box compare function - [] add barrier support to _value, and _signal - s.barrier -> synonym to .flow.filter.untrack for values, **but different for signals** - s.barrier.not -> synonym to .flow.filter.not.untrack for values, but different for signals - ... hmm.. on thinking - ... - Is "s.filter" instead of "s.flow.filter" or should be both? + [] ==> + move .flow.filter to .filter -- .filter returns same object with "set" if it was available before + for signal: .filter and .filter.not untracked by default and returns with signal comparer + for signal: .filter.track, .filter.not.track + for signal: .flow returns with signal comparer, .flow tracks by default same as in value + + .pre.filter tracked by default for both cases, because it can be used in "cycle", and + necessary to register dependency for evaluation scope context. + === + make ".map" alias for ".view" [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] x.combine([a,b,c]) -> [x,a,b,c] [] x.select.multiple({a:fn, b:fn}).group((ctx)=> {ctx.a.to(m); ctx.b.to(p)}).group() [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds + [] signal.touchable(initial) [] x.group -- x.op -- x.block x.block((ctx) => ({ // if returns non undefined a: ctx.a, From 8c99146ea7d24deabdbbe83fcf8c3c725598b10d Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 11:10:27 +0700 Subject: [PATCH 034/100] feat: .flow.filter moved to .filter --- src/index.ts | 64 +++++++++++++++++++--------------------------- tests/_api.test.ts | 26 +++++++++---------- 2 files changed, 39 insertions(+), 51 deletions(-) diff --git a/src/index.ts b/src/index.ts index f44793de..02b48628 100644 --- a/src/index.ts +++ b/src/index.ts @@ -98,31 +98,22 @@ const def_prop = Object.defineProperty; /* TODOs: + [] .filter returns same object with "set" if it was available before + [] signal <- purpose to implement signal through box compare function [] ==> - move .flow.filter to .filter -- .filter returns same object with "set" if it was available before for signal: .filter and .filter.not untracked by default and returns with signal comparer - for signal: .filter.track, .filter.not.track - for signal: .flow returns with signal comparer, .flow tracks by default same as in value - - .pre.filter tracked by default for both cases, because it can be used in "cycle", and - necessary to register dependency for evaluation scope context. + for signal: .filter.track, .filter.not.track opposed to .untrack for value === - make ".map" alias for ".view" - [] v.as.value(), v.as.signal(), v.as.trigger.flag() + [] x.select.multiple({a:fn, b:fn}) + [] .combine [] x.combine([a,b,c]) -> [x,a,b,c] - [] x.select.multiple({a:fn, b:fn}).group((ctx)=> {ctx.a.to(m); ctx.b.to(p)}).group() [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) - [] x.group -- x.op -- x.block - x.block((ctx) => ({ // if returns non undefined - a: ctx.a, - b: ctx.a.select() - })).b.val + [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] ... [] combine as root level exportable factory function - [] flow as root level exportable factory function Backlog [] .view.untrack @@ -131,10 +122,16 @@ const def_prop = Object.defineProperty; [] .pre.filter.not.untrack [] v.as.readonly() [] flow.resolve + [] flow as root level exportable factory function + [] .flow -- (for signal) .flow returns with signal comparer, .flow tracks by default same as in value [] .chan - [] .combine [] value.trigger.flag.from [] .map <- sysnonym for .view (on thinking) + [] x.group -- x.op -- x.block + x.block((ctx) => ({ // if returns non undefined + a: ctx.a, + b: ctx.a.select() + })).b.val */ @@ -441,7 +438,7 @@ const trait_ent_flow = (ctx, fn) => { h[1] ? proto_entity_writtable : proto_entity_readable ); }; -const trait_ent_flow_filter = (ctx, fn) => ( +const trait_ent_filter = (ctx, fn) => ( trait_ent_flow(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), (v, prev) => ( @@ -450,8 +447,8 @@ const trait_ent_flow_filter = (ctx, fn) => ( : (v) => v || flow_stop ) ); -const trait_ent_flow_filter_not = (ctx, fn) => ( - trait_ent_flow_filter(ctx, fn +const trait_ent_filter_not = (ctx, fn) => ( + trait_ent_filter(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), (v) => !fn(v)) : pure_arrow_fn_returns_not_arg) ); @@ -463,28 +460,18 @@ const trait_ent_flow_filter_not = (ctx, fn) => ( const proto_entity_readable_to_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); -// readable.flow.filter:ns -// .flow.filter.not -const proto_entity_writtable_flow_filter_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_flow_filter_ns, key_not, trait_ent_flow_filter_not); +// readable.filter:ns +// .filter.not +const proto_entity_readable_filter_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_readable_filter_ns, key_not, trait_ent_filter_not); -// readable.flow:ns -// .flow.filter:readable.flow.filter:ns -const proto_entity_readable_flow_ns = obj_create(pure_fn); -obj_def_prop_trait_ns_with_ns( - proto_entity_readable_flow_ns, - key_filter, - trait_ent_flow_filter, - proto_entity_writtable_flow_filter_ns -); // readable // .sync // .to:readable.to:ns // .to.once -// .flow:readable.flow:ns -// .flow.filter:readable.flow.filter:ns -// flow.filter.not +// .filter:readable.filter:ns +// .filter.not // .select // .view // .promise @@ -498,10 +485,11 @@ obj_def_prop_trait_with_ns( ); obj_def_prop_trait_with_ns( proto_entity_readable, - key_flow, - trait_ent_flow, - proto_entity_readable_flow_ns + key_filter, + trait_ent_filter, + proto_entity_readable_filter_ns ); +obj_def_prop_trait(proto_entity_readable, key_flow, trait_ent_flow); obj_def_prop_trait(proto_entity_readable, key_select, trait_ent_select); obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); obj_def_prop_promise(proto_entity_readable); diff --git a/tests/_api.test.ts b/tests/_api.test.ts index dbc2239e..789b6da1 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -342,15 +342,15 @@ test('should work _value with flow', () => { }); -test('should work _value with flow.filter, flow.filter.not', () => { +test('should work _value with .filter, .filter.not', () => { let t; const v = _value(5); const f = _value(0); - const w = v.flow.filter((_v) => _v !== 10); - const k = ((t = w.flow.filter), (t = t.not), t(f)); - const m = k.flow.filter(); - const n = ((t = k.flow), (t = t.filter.not), t()); + const w = v.filter((_v) => _v !== 10); + const k = ((t = w.filter), (t = t.not), (t = t(f))); + const m = k.filter(); + const n = ((t = t.filter.not), t()); expect(n.dirty).toBeUndefined(); expect(m.reset).toBeUndefined(); @@ -555,8 +555,8 @@ test('should work _selector basic support', () => { expect(s.to).not.toBeUndefined(); expect(s.to.once).not.toBeUndefined(); expect(s.flow).not.toBeUndefined(); - expect(s.flow.filter).not.toBeUndefined(); - expect(s.flow.filter.not).not.toBeUndefined(); + expect(s.filter).not.toBeUndefined(); + expect(s.filter.not).not.toBeUndefined(); expect(s.view).not.toBeUndefined(); expect(s.promise).not.toBeUndefined(); @@ -587,13 +587,13 @@ test('should work _selector basic support', () => { expect(b.val).toBe(102); }); -test('should work _selector with to, flow, view', () => { +test('should work _selector with to, filter, view', () => { let t; const spy = jest.fn(); const b = _value(1); const v = _value(0); const s = _selector(() => v.val + 1); - const f = s.flow.filter.not(b); + const f = s.filter.not(b); const w = s.view((v) => v + 5); (t = s.to), t(spy); @@ -628,8 +628,8 @@ test('should work _value.from with one argument', () => { expect(typeof v).toBe('object'); expect(v.to.once).not.toBeUndefined(); expect(v.flow).not.toBeUndefined(); - expect(v.flow.filter).not.toBeUndefined(); - expect(v.flow.filter.not).not.toBeUndefined(); + expect(v.filter).not.toBeUndefined(); + expect(v.filter.not).not.toBeUndefined(); expect(v.view).not.toBeUndefined(); expect(v.promise).not.toBeUndefined(); @@ -659,8 +659,8 @@ test('should work _value.from with two arguments', () => { expect(v.sync).not.toBeUndefined(); expect(v.to.once).not.toBeUndefined(); expect(v.flow).not.toBeUndefined(); - expect(v.flow.filter).not.toBeUndefined(); - expect(v.flow.filter.not).not.toBeUndefined(); + expect(v.filter).not.toBeUndefined(); + expect(v.filter.not).not.toBeUndefined(); expect(v.view).not.toBeUndefined(); expect(v.set).not.toBeUndefined(); expect(v.update).not.toBeUndefined(); From 3e59f12034c22a45cda9f1625fd0edab1022c6bf Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 14:27:31 +0700 Subject: [PATCH 035/100] feat: select.multiple, signal, and track/untrack added --- src/index.ts | 255 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 185 insertions(+), 70 deletions(-) diff --git a/src/index.ts b/src/index.ts index 02b48628..53891c02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,12 @@ import React, { Context, FC } from 'react'; -import { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; +import rb, { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; export { _value, _selector, _transaction, + _signal, + _untrack, value, selector, @@ -98,15 +100,23 @@ const def_prop = Object.defineProperty; /* TODOs: - [] .filter returns same object with "set" if it was available before - - [] signal <- purpose to implement signal through box compare function - [] ==> - for signal: .filter and .filter.not untracked by default and returns with signal comparer - for signal: .filter.track, .filter.not.track opposed to .untrack for value - === - - [] x.select.multiple({a:fn, b:fn}) + [] Tests for + signal, + all track/untrack functions, + [] .view.untrack + [] .pre.untrack + [] .pre.filter.untrack + [] .pre.filter.not.untrack + [] .filter.track + [] .filter.not.track + etc. + signal.from + signal.trigger, + signal.trigger.flag + check .filter and .filter.not untracked by default + x.select.multiple({a:fn, b:fn}) + + === [] .combine [] x.combine([a,b,c]) -> [x,a,b,c] [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds @@ -116,15 +126,11 @@ const def_prop = Object.defineProperty; [] combine as root level exportable factory function Backlog - [] .view.untrack - [] .pre.untrack - [] .pre.filter.untrack - [] .pre.filter.not.untrack [] v.as.readonly() [] flow.resolve [] flow as root level exportable factory function - [] .flow -- (for signal) .flow returns with signal comparer, .flow tracks by default same as in value [] .chan + [] value.trigger.from [] value.trigger.flag.from [] .map <- sysnonym for .view (on thinking) [] x.group -- x.op -- x.block @@ -145,13 +151,17 @@ const def_prop = Object.defineProperty; const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; +const obj_keys = Object.keys; const new_symbol = Symbol; +const const_undef = undefined; // // Reactive box specific definitions. // -const flow_stop = flow.stop; +const internal_flow_stop = flow.stop; +const internal_untrack = rb.untrack; +const internal_transaction = rb.transaction; // // Entity builder for value, signal and etc. Typings. @@ -175,6 +185,7 @@ type ValueFactory = { type SelectorFactory = { (fn: () => any): any; } +type SignalFactory = ValueFactory; // @@ -184,6 +195,7 @@ type SelectorFactory = { const pure_fn = function () {}; const pure_arrow_fn_returns_arg = (v) => v; const pure_arrow_fn_returns_not_arg = (v) => !v; +const pure_arrow_fn_returns_true = () => true; const key_proto = "__proto__"; const key_get = "get"; @@ -215,7 +227,10 @@ const key_trigger = "trigger"; const key_flag = "flag"; const key_invert = "invert"; const key_from = "from"; - +const key_is_signal = new_symbol(); +const key_track = "track"; +const key_untrack = "untrack"; +const key_multiple = "multiple"; const obj_def_prop_value = (obj, key, value) => ( @@ -225,32 +240,32 @@ const obj_def_prop_value = (obj, key, value) => ( const obj_def_prop_trait = (obj, key, trait) => obj_def_prop(obj, key, { get() { - return obj_def_prop_value(this, key, trait.bind(void 0, this)); + return obj_def_prop_value(this, key, trait.bind(const_undef, this)); } }); const obj_def_prop_trait_ns = (obj, key, trait) => obj_def_prop(obj, key, { get() { - return obj_def_prop_value(this, key, trait.bind(void 0, this[key_ctx])); + return obj_def_prop_value(this, key, trait.bind(const_undef, this[key_ctx])); } }); -const obj_def_prop_trait_ns_with_ns = (obj, key, trait, ns) => +const obj_def_prop_trait_ns_with_ns = (obj, key, trait, ns, is_trait_op?) => obj_def_prop(obj, key, { get() { const ctx = this[key_ctx]; - const ret = trait.bind(void 0, ctx); + const ret = (is_trait_op ? trait(ctx) : trait).bind(const_undef, ctx); ret[key_proto] = ns; ret[key_ctx] = ctx; return obj_def_prop_value(this, key, ret); } }); -const obj_def_prop_trait_with_ns = (obj, key, trait, ns) => +const obj_def_prop_trait_with_ns = (obj, key, trait, ns, is_trait_op?) => obj_def_prop(obj, key, { get() { - const ret = trait.bind(void 0, this); + const ret = (is_trait_op ? trait(this) : trait).bind(const_undef, this); ret[key_proto] = ns; ret[key_ctx] = this; return obj_def_prop_value(this, key, ret); @@ -283,6 +298,7 @@ const obj_def_prop_promise = (obj) => { }; + const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { let set = _set || handler[1]; let get = _get || handler[0]; @@ -303,6 +319,23 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { return ctx; } +const make_trait_ent_untrack = (trait_fn) => + (ctx, fn) => trait_fn(ctx, (a,b) => { + const finish = internal_untrack(); + try { return fn(a,b) } + finally { finish() } + }); + +const op_trait_if_signal = (trait_if_not_signal, trait_if_signal) => ( + (ctx) => ctx[key_handler][key_is_signal] ? trait_if_signal : trait_if_not_signal +); + +const make_proto_for_trackable = (trait_track, trait_untrack) => ( + obj_def_prop_trait( + obj_def_prop_trait(obj_create(pure_fn), key_track, trait_track), + key_untrack, trait_untrack) +); + const prop_factory_dirty_required_initial = (ctx) => { const h = ctx[key_handler]; @@ -320,6 +353,7 @@ const prop_factory_dirty_required_initial = (ctx) => { const trait_ent_update = (ctx, fn) => (ctx[key_set](fn && fn(ctx[key_get]()))); +const trait_ent_update_untrack = make_trait_ent_untrack(trait_ent_update); const trait_ent_update_by = (ctx, src, fn) => { const src_get = src[key_get] ? src[key_get] : src; const e = expr(src_get, fn @@ -388,6 +422,13 @@ const trait_ent_to_once = (ctx, fn) => { const trait_ent_select = (ctx, fn) => ( fill_entity(sel(fn ? () => fn(ctx[key_get]()) : ctx[key_get]).slice(0, 1), proto_entity_readable) ); +const trait_ent_select_untrack = make_trait_ent_untrack(trait_ent_select); +const trait_ent_select_multiple = (ctx, cfg) => obj_keys(cfg).reduce((ret, key) => ( + (ret[key] = trait_ent_select(ctx, cfg[key])), ret +), {}); +const trait_ent_select_multiple_untrack = (ctx, cfg) => obj_keys(cfg).reduce((ret, key) => ( + (ret[key] = trait_ent_select_untrack(ctx, cfg[key])), ret +), {}); const trait_ent_view = (ctx, fn) => ( fill_entity(ctx[key_handler], ctx[key_proto], 0, 0, @@ -395,6 +436,7 @@ const trait_ent_view = (ctx, fn) => ( ctx[key_set] && ctx[key_set].bind() ) ); +const trait_ent_view_untrack = make_trait_ent_untrack(trait_ent_view); const trait_ent_pre = (ctx, fn) => ( fn ? fill_entity(ctx[key_handler], ctx[key_proto], @@ -404,6 +446,7 @@ const trait_ent_pre = (ctx, fn) => ( ) : ctx ); +const trait_ent_pre_untrack = make_trait_ent_untrack(trait_ent_pre); const trait_ent_pre_filter = (ctx, fn) => ( (fn = fn ? (fn[key_get] ? fn[key_get] : fn) @@ -414,6 +457,7 @@ const trait_ent_pre_filter = (ctx, fn) => ( (v) => fn(v) && ctx[key_set](v) ) ); +const trait_ent_pre_filter_untrack = make_trait_ent_untrack(trait_ent_pre_filter); const trait_ent_pre_filter_not = (ctx, fn) => ( trait_ent_pre_filter(ctx, fn ? (fn[key_get] @@ -421,6 +465,7 @@ const trait_ent_pre_filter_not = (ctx, fn) => ( : (v) => !fn(v)) : pure_arrow_fn_returns_not_arg) ); +const trait_ent_pre_filter_not_untrack = make_trait_ent_untrack(trait_ent_pre_filter_not); const trait_ent_flow = (ctx, fn) => { fn || (fn = pure_arrow_fn_returns_arg); @@ -429,29 +474,33 @@ const trait_ent_flow = (ctx, fn) => { const v = ctx[key_get](); try { return fn(v, prev) } finally { prev = v } - }); + }, const_undef, ctx[key_is_signal] && pure_arrow_fn_returns_true); const h = [ () => ((started || (f[0](), (started = 1))), f[1]()), ctx[key_set] && ctx[key_set].bind() ]; + h[key_is_signal] = ctx[key_is_signal]; return fill_entity(h, h[1] ? proto_entity_writtable : proto_entity_readable ); }; +const trait_ent_flow_untrack = make_trait_ent_untrack(trait_ent_flow); const trait_ent_filter = (ctx, fn) => ( trait_ent_flow(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), (v, prev) => ( - fn(v, prev) ? v : flow_stop + fn(v, prev) ? v : internal_flow_stop )) - : (v) => v || flow_stop + : (v) => v || internal_flow_stop ) ); +const trait_ent_filter_untrack = make_trait_ent_untrack(trait_ent_filter) const trait_ent_filter_not = (ctx, fn) => ( trait_ent_filter(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), (v) => !fn(v)) : pure_arrow_fn_returns_not_arg) ); +const trait_ent_filter_not_untrack = make_trait_ent_untrack(trait_ent_filter_not) @@ -460,20 +509,37 @@ const trait_ent_filter_not = (ctx, fn) => ( const proto_entity_readable_to_ns = obj_create(pure_fn); obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); -// readable.filter:ns -// .filter.not -const proto_entity_readable_filter_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_readable_filter_ns, key_not, trait_ent_filter_not); +// readable.filter:ns (track|untrack) +// .filter.not (track|untrack) +const proto_entity_readable_filter_ns = obj_create( + make_proto_for_trackable(trait_ent_filter, trait_ent_filter_untrack) +); +obj_def_prop_trait_ns_with_ns(proto_entity_readable_filter_ns, key_not, + op_trait_if_signal(trait_ent_filter_not, trait_ent_filter_not_untrack), + make_proto_for_trackable(trait_ent_filter_not, trait_ent_filter_not_untrack), + 1 +); + +// readable.select:ns (track|untrack) +// .select.multiple (track|untrack) +const proto_entity_readable_select_ns = obj_create( + make_proto_for_trackable(trait_ent_select, trait_ent_select_untrack) +); +obj_def_prop_trait_ns_with_ns(proto_entity_readable_select_ns, key_multiple, trait_ent_select_multiple, + make_proto_for_trackable(trait_ent_select_multiple, trait_ent_select_multiple_untrack) +); // readable // .sync // .to:readable.to:ns // .to.once -// .filter:readable.filter:ns -// .filter.not -// .select -// .view +// .filter:readable.filter:ns (track|untrack) +// .filter.not (track|untrack) +// .select:readable.select:ns (track|untrack) +// .select.multiple (track|untrack) +// .view (track|untrack) +// .flow (track|untrack) // .promise const proto_entity_readable = obj_create(pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); @@ -486,27 +552,52 @@ obj_def_prop_trait_with_ns( obj_def_prop_trait_with_ns( proto_entity_readable, key_filter, - trait_ent_filter, - proto_entity_readable_filter_ns + op_trait_if_signal(trait_ent_filter, trait_ent_filter_untrack), + proto_entity_readable_filter_ns, + 1 +); +obj_def_prop_trait_with_ns( + proto_entity_readable, + key_flow, + op_trait_if_signal(trait_ent_flow, trait_ent_flow_untrack), + make_proto_for_trackable(trait_ent_flow, trait_ent_flow_untrack), + 1 +); +obj_def_prop_trait_with_ns( + proto_entity_readable, + key_select, + trait_ent_select, + make_proto_for_trackable(trait_ent_select, trait_ent_select_untrack), +); +obj_def_prop_trait_with_ns( + proto_entity_readable, + key_view, + trait_ent_view, + make_proto_for_trackable(trait_ent_select, trait_ent_view_untrack), ); -obj_def_prop_trait(proto_entity_readable, key_flow, trait_ent_flow); -obj_def_prop_trait(proto_entity_readable, key_select, trait_ent_select); -obj_def_prop_trait(proto_entity_readable, key_view, trait_ent_view); obj_def_prop_promise(proto_entity_readable); -// writtable.update:ns +// writtable.update:ns (track|untrack) // .update.by -const proto_entity_writtable_update_ns = obj_create(pure_fn); +const proto_entity_writtable_update_ns = obj_create( + make_proto_for_trackable(trait_ent_update, trait_ent_update_untrack) +); obj_def_prop_trait_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by); -// writtable.pre.filter:ns -// .pre.filter.not -const proto_entity_writtable_pre_filter_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_pre_filter_ns, key_not, trait_ent_pre_filter_not); +// writtable.pre.filter:ns (track|untrack) +// .pre.filter.not (track|untrack) +const proto_entity_writtable_pre_filter_ns = obj_create( + make_proto_for_trackable(trait_ent_pre_filter, trait_ent_pre_filter_untrack) +); +obj_def_prop_trait_ns_with_ns(proto_entity_writtable_pre_filter_ns, key_not, trait_ent_pre_filter_not, + make_proto_for_trackable(trait_ent_pre_filter_not, trait_ent_pre_filter_not_untrack) +); -// writtable.pre:ns -// .pre.filter:writtable.pre.filter:ns -const proto_entity_writtable_pre_ns = obj_create(pure_fn); +// writtable.pre:ns (track|untrack) +// .pre.filter:writtable.pre.filter:ns (track|untrack) +const proto_entity_writtable_pre_ns = obj_create( + make_proto_for_trackable(trait_ent_pre, trait_ent_pre_untrack) +); obj_def_prop_trait_ns_with_ns( proto_entity_writtable_pre_ns, key_filter, @@ -515,11 +606,11 @@ obj_def_prop_trait_ns_with_ns( ); // writtable <- readable -// .update:writtable.update:ns +// .update:writtable.update:ns (track|untrack) // .update.by -// .pre:writtable.pre:ns -// .pre.filter:writtable.pre.filter:ns -// pre.filter.not +// .pre:writtable.pre:ns (track|untrack) +// .pre.filter:writtable.pre.filter:ns (track|untrack) +// pre.filter.not (track|untrack) const proto_entity_writtable = obj_create(proto_entity_readable); obj_def_prop_trait_with_ns( proto_entity_writtable, @@ -572,9 +663,9 @@ obj_def_prop_factory( -const make_trigger = (initial, has_to?) => { - const handler = box(initial, () => (handler[key_touched_internal] = 1)); - const set = has_to +const make_trigger = (initial, has_inverted_to?, is_signal?) => { + const handler = box(initial, () => (handler[key_touched_internal] = 1), is_signal && pure_arrow_fn_returns_true); + const set = has_inverted_to ? () => { handler[key_touched_internal] || handler[1](!handler[0]()) } : (v) => { handler[key_touched_internal] || handler[1](v) }; handler[key_reset_promise_by_reset] = 1; @@ -583,28 +674,46 @@ const make_trigger = (initial, has_to?) => { +const _value: ValueFactory = ((initial) => ( + fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) +)) as any; + const value_trigger = (initial) => make_trigger(initial); const value_trigger_flag = (initial) => make_trigger(!!initial, 1); const value_trigger_flag_invert = (initial) => make_trigger(!initial, 1); const value_from = (get, set?) => ( - (get = sel(get).slice(0, 1), set && (get = get.concat(set))), + (get = sel(get).slice(0, 1), set && (get[1] = set)), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) -) +); + +value_trigger_flag[key_invert] = value_trigger_flag_invert; +value_trigger[key_flag] = value_trigger_flag; +_value[key_trigger] = value_trigger as any; +_value[key_from] = value_from; + const _selector: SelectorFactory = (fn) => ( fill_entity(sel(fn).slice(0, 1), proto_entity_readable) ) -const _value: ValueFactory = ((initial) => ( - fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) -)) as any; - - +const _signal: SignalFactory = ((initial) => { + const h = box(initial, 0 as any, pure_arrow_fn_returns_true); + h[key_is_signal] = 1; + return fill_entity(h, proto_entity_writtable_leaf, 1, initial) +}) as any; + +const signal_trigger = (initial) => make_trigger(initial, 0, 1); +const signal_trigger_flag = (initial) => make_trigger(!!initial, 1, 1); +const signal_trigger_flag_invert = (initial) => make_trigger(!initial, 1, 1); +const _signal_from = (get, set?) => ( + (get = [get], (get[key_is_signal] = 1), set && (get[1] = set)), + fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) +); -value_trigger_flag[key_invert] = value_trigger_flag_invert; -value_trigger[key_flag] = value_trigger_flag; -_value[key_trigger] = value_trigger as any; -_value[key_from] = value_from; +signal_trigger_flag[key_invert] = signal_trigger_flag_invert; +signal_trigger[key_flag] = signal_trigger_flag; +_signal[key_trigger] = signal_trigger as any; +_signal[key_from] = _signal_from; // @@ -612,7 +721,13 @@ _value[key_from] = value_from; // const _transaction = (fn) => { - const finish = transaction(); + const finish = internal_transaction(); + try { return fn() } + finally { finish() } +}; + +const _untrack = (fn) => { + const finish = internal_untrack(); try { return fn() } finally { finish() } }; @@ -981,9 +1096,9 @@ function def_format( ctx.set = set; if (!readonly_val) val_prop.set = set; - ctx.sub = (s: any, fn: any) => on(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : void 0)); + ctx.sub = (s: any, fn: any) => on(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : const_undef)); ctx.sub.once = (s: any, fn: any) => - once(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : void 0)); + once(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : const_undef)); } def_prop(ctx, key, val_prop); @@ -1399,7 +1514,7 @@ function free() { call_array(shared_unsubs); } finally { shareds.clear(); - initial_data = void 0; + initial_data = const_undef; } } From 79d109e3f3dd24db37fe741574bde509b7c01a3a Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 14:39:33 +0700 Subject: [PATCH 036/100] chore: some updates --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 53891c02..a1dfe12a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -113,7 +113,7 @@ const def_prop = Object.defineProperty; signal.from signal.trigger, signal.trigger.flag - check .filter and .filter.not untracked by default + check .filter and .filter.not untracked by default for signal x.select.multiple({a:fn, b:fn}) === @@ -153,7 +153,7 @@ const obj_def_prop = Object.defineProperty; const obj_create = Object.create; const obj_keys = Object.keys; const new_symbol = Symbol; -const const_undef = undefined; +const const_undef = void 0; // // Reactive box specific definitions. From 82c9c157dc162e5bb5bd2bd367024a66431c7c07 Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 14:52:17 +0700 Subject: [PATCH 037/100] chore: todos updated --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index a1dfe12a..4358091d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -117,11 +117,11 @@ const def_prop = Object.defineProperty; x.select.multiple({a:fn, b:fn}) === + [] signal(0).as.value() [] .combine [] x.combine([a,b,c]) -> [x,a,b,c] [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) - [] v.as.value(), v.as.signal(), v.as.trigger.flag() [] ... [] combine as root level exportable factory function @@ -138,6 +138,7 @@ const def_prop = Object.defineProperty; a: ctx.a, b: ctx.a.select() })).b.val + [] v.as.signal(), v.as.trigger.flag() */ From 040647e29ad50cd66dc76ae982fb3ae19fc74a3d Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 15:53:56 +0700 Subject: [PATCH 038/100] feat: _value.select.multiple test case added --- src/index.ts | 3 +-- tests/_api.test.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4358091d..21540a74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,7 +114,6 @@ const def_prop = Object.defineProperty; signal.trigger, signal.trigger.flag check .filter and .filter.not untracked by default for signal - x.select.multiple({a:fn, b:fn}) === [] signal(0).as.value() @@ -568,7 +567,7 @@ obj_def_prop_trait_with_ns( proto_entity_readable, key_select, trait_ent_select, - make_proto_for_trackable(trait_ent_select, trait_ent_select_untrack), + proto_entity_readable_select_ns, ); obj_def_prop_trait_with_ns( proto_entity_readable, diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 789b6da1..1bf80f1c 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1,4 +1,4 @@ -import { _value, _selector, _transaction } from '../src'; +import { _value, _selector, _transaction, loop } from '../src'; test('should work _value with call, get, set, update, sync', () => { const spy = jest.fn(); @@ -679,6 +679,45 @@ test('should work _value.from with two arguments', () => { expect(spy).toBeCalledWith(3, 2); spy.mockReset(); }); +test('should work _value.select.multiple', () => { + let t; + const k = _value(0); + const v = _value(1); + (t = v.select.multiple), t = t({ + a: (_v) => _v + 1, + b: (_v) => _v + k.val, + }); + + const { a, b } = t; + + expect(a.val).toBe(2); + expect(b.val).toBe(1); + v(2); + expect(a.val).toBe(3); + expect(b.val).toBe(2); + k(2); + expect(a.val).toBe(3); + expect(b.val).toBe(4); + + [a,b].forEach((v) => { + expect(typeof v).toBe('object'); + expect(v.sync).not.toBeUndefined(); + expect(v.to).not.toBeUndefined(); + expect(v.to.once).not.toBeUndefined(); + expect(v.flow).not.toBeUndefined(); + expect(v.filter).not.toBeUndefined(); + expect(v.filter.not).not.toBeUndefined(); + expect(v.view).not.toBeUndefined(); + expect(v.promise).not.toBeUndefined(); + + expect(v.set).toBeUndefined(); + expect(v.update).toBeUndefined(); + expect(v.pre).toBeUndefined(); + expect(v.reset).toBeUndefined(); + expect(v.reinit).toBeUndefined(); + expect(v.dirty).toBeUndefined(); + }); +}); From abf1ea524054288a5913fafe6d086eef4a65c88a Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 16:08:27 +0700 Subject: [PATCH 039/100] feat: _value with select and select.multiple track-untrack cases added --- src/index.ts | 27 ++++++++++++----------- tests/_api.test.ts | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 21540a74..e37bc352 100644 --- a/src/index.ts +++ b/src/index.ts @@ -319,6 +319,7 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { return ctx; } + const make_trait_ent_untrack = (trait_fn) => (ctx, fn) => trait_fn(ctx, (a,b) => { const finish = internal_untrack(); @@ -330,9 +331,9 @@ const op_trait_if_signal = (trait_if_not_signal, trait_if_signal) => ( (ctx) => ctx[key_handler][key_is_signal] ? trait_if_signal : trait_if_not_signal ); -const make_proto_for_trackable = (trait_track, trait_untrack) => ( - obj_def_prop_trait( - obj_def_prop_trait(obj_create(pure_fn), key_track, trait_track), +const make_proto_for_trackable_ns = (trait_track, trait_untrack) => ( + obj_def_prop_trait_ns( + obj_def_prop_trait_ns(obj_create(pure_fn), key_track, trait_track), key_untrack, trait_untrack) ); @@ -512,21 +513,21 @@ obj_def_prop_trait_ns(proto_entity_readable_to_ns, key_once, trait_ent_to_once); // readable.filter:ns (track|untrack) // .filter.not (track|untrack) const proto_entity_readable_filter_ns = obj_create( - make_proto_for_trackable(trait_ent_filter, trait_ent_filter_untrack) + make_proto_for_trackable_ns(trait_ent_filter, trait_ent_filter_untrack) ); obj_def_prop_trait_ns_with_ns(proto_entity_readable_filter_ns, key_not, op_trait_if_signal(trait_ent_filter_not, trait_ent_filter_not_untrack), - make_proto_for_trackable(trait_ent_filter_not, trait_ent_filter_not_untrack), + make_proto_for_trackable_ns(trait_ent_filter_not, trait_ent_filter_not_untrack), 1 ); // readable.select:ns (track|untrack) // .select.multiple (track|untrack) const proto_entity_readable_select_ns = obj_create( - make_proto_for_trackable(trait_ent_select, trait_ent_select_untrack) + make_proto_for_trackable_ns(trait_ent_select, trait_ent_select_untrack) ); obj_def_prop_trait_ns_with_ns(proto_entity_readable_select_ns, key_multiple, trait_ent_select_multiple, - make_proto_for_trackable(trait_ent_select_multiple, trait_ent_select_multiple_untrack) + make_proto_for_trackable_ns(trait_ent_select_multiple, trait_ent_select_multiple_untrack) ); @@ -560,7 +561,7 @@ obj_def_prop_trait_with_ns( proto_entity_readable, key_flow, op_trait_if_signal(trait_ent_flow, trait_ent_flow_untrack), - make_proto_for_trackable(trait_ent_flow, trait_ent_flow_untrack), + make_proto_for_trackable_ns(trait_ent_flow, trait_ent_flow_untrack), 1 ); obj_def_prop_trait_with_ns( @@ -573,30 +574,30 @@ obj_def_prop_trait_with_ns( proto_entity_readable, key_view, trait_ent_view, - make_proto_for_trackable(trait_ent_select, trait_ent_view_untrack), + make_proto_for_trackable_ns(trait_ent_select, trait_ent_view_untrack), ); obj_def_prop_promise(proto_entity_readable); // writtable.update:ns (track|untrack) // .update.by const proto_entity_writtable_update_ns = obj_create( - make_proto_for_trackable(trait_ent_update, trait_ent_update_untrack) + make_proto_for_trackable_ns(trait_ent_update, trait_ent_update_untrack) ); obj_def_prop_trait_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by); // writtable.pre.filter:ns (track|untrack) // .pre.filter.not (track|untrack) const proto_entity_writtable_pre_filter_ns = obj_create( - make_proto_for_trackable(trait_ent_pre_filter, trait_ent_pre_filter_untrack) + make_proto_for_trackable_ns(trait_ent_pre_filter, trait_ent_pre_filter_untrack) ); obj_def_prop_trait_ns_with_ns(proto_entity_writtable_pre_filter_ns, key_not, trait_ent_pre_filter_not, - make_proto_for_trackable(trait_ent_pre_filter_not, trait_ent_pre_filter_not_untrack) + make_proto_for_trackable_ns(trait_ent_pre_filter_not, trait_ent_pre_filter_not_untrack) ); // writtable.pre:ns (track|untrack) // .pre.filter:writtable.pre.filter:ns (track|untrack) const proto_entity_writtable_pre_ns = obj_create( - make_proto_for_trackable(trait_ent_pre, trait_ent_pre_untrack) + make_proto_for_trackable_ns(trait_ent_pre, trait_ent_pre_untrack) ); obj_def_prop_trait_ns_with_ns( proto_entity_writtable_pre_ns, diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 1bf80f1c..131b7bcf 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -719,6 +719,61 @@ test('should work _value.select.multiple', () => { }); }); +test('should work track-untrack for _value with select', () => { + const a = _value(0); + + const v = _value(0); + const p = v.select((_v) => _v + a.val); + const t = v.select.track((_v) => _v + a.val); + const u = v.select.untrack((_v) => _v + a.val) + + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + v(1); + expect(p.val).toBe(1); + expect(t.val).toBe(1); + expect(u.val).toBe(1); + a(1); + expect(p.val).toBe(2); + expect(t.val).toBe(2); + expect(u.val).toBe(1); +}); + +test('should work track-untrack for _value with select.multiple', () => { + const a = _value(0); + + const v = _value(0); + const p = v.select.multiple({ p: (_v) => _v + a.val }).p; + const t = v.select.multiple.track({t: (_v) => _v + a.val }).t; + const u = v.select.multiple.untrack({u: (_v) => _v + a.val }).u; + + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + v(1); + expect(p.val).toBe(1); + expect(t.val).toBe(1); + expect(u.val).toBe(1); + a(1); + expect(p.val).toBe(2); + expect(t.val).toBe(2); + expect(u.val).toBe(1); +}); + + + + + + + + + + + + + + From fa386f2826b02cf3d76c1fce1051d5fb2caa301c Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 16:24:09 +0700 Subject: [PATCH 040/100] feat: _value with filter and filter.not track-untrack cases added --- src/index.ts | 6 ++-- tests/_api.test.ts | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index e37bc352..308bb8bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -107,8 +107,6 @@ const def_prop = Object.defineProperty; [] .pre.untrack [] .pre.filter.untrack [] .pre.filter.not.untrack - [] .filter.track - [] .filter.not.track etc. signal.from signal.trigger, @@ -323,7 +321,7 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { const make_trait_ent_untrack = (trait_fn) => (ctx, fn) => trait_fn(ctx, (a,b) => { const finish = internal_untrack(); - try { return fn(a,b) } + try { return fn[key_get] ? fn[key_get]() : fn(a,b) } finally { finish() } }); @@ -462,7 +460,7 @@ const trait_ent_pre_filter_untrack = make_trait_ent_untrack(trait_ent_pre_filter const trait_ent_pre_filter_not = (ctx, fn) => ( trait_ent_pre_filter(ctx, fn ? (fn[key_get] - ? (v) => !fn[key_get](v) + ? () => !fn[key_get]() : (v) => !fn(v)) : pure_arrow_fn_returns_not_arg) ); diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 131b7bcf..3e1d9174 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -761,9 +761,78 @@ test('should work track-untrack for _value with select.multiple', () => { expect(u.val).toBe(1); }); +test('should work track-untrack for _value with flow', () => { + const a = _value(0); + + const v = _value(0); + const p = v.flow((_v) => _v + a.val); + const t = v.flow.track((_v) => _v + a.val); + const u = v.flow.untrack((_v) => _v + a.val) + + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + v(1); + expect(p.val).toBe(1); + expect(t.val).toBe(1); + expect(u.val).toBe(1); + a(1); + expect(p.val).toBe(2); + expect(t.val).toBe(2); + expect(u.val).toBe(1); +}); + +test('should work track-untrack for _value with filter', () => { + const a = _value(1); + + const v = _value(0); + const p = v.filter(a); + const t = v.filter.track(a); + const u = v.filter.untrack(a); + + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + a(0); + v(1); + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + a(1); + expect(p.val).toBe(1); + expect(t.val).toBe(1); + expect(u.val).toBe(0); + v(2); + expect(p.val).toBe(2); + expect(t.val).toBe(2); + expect(u.val).toBe(2); +}); +test('should work track-untrack for _value with filter.not', () => { + const a = _value(0); + const v = _value(0); + const p = v.filter.not(a); + const t = v.filter.not.track(a); + const u = v.filter.not.untrack(a); + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + a(1); + v(1); + expect(p.val).toBe(0); + expect(t.val).toBe(0); + expect(u.val).toBe(0); + a(0); + expect(p.val).toBe(1); + expect(t.val).toBe(1); + expect(u.val).toBe(0); + v(2); + expect(p.val).toBe(2); + expect(t.val).toBe(2); + expect(u.val).toBe(2); +}); From f6d38caee8c0b5aa1cd789766855ca3b32e1588e Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 16:29:29 +0700 Subject: [PATCH 041/100] chore: todos updated --- src/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 308bb8bb..e775a27e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,17 +101,18 @@ const def_prop = Object.defineProperty; /* TODOs: [] Tests for - signal, - all track/untrack functions, + all track/untrack functions [] .view.untrack [] .pre.untrack [] .pre.filter.untrack [] .pre.filter.not.untrack etc. + signal signal.from - signal.trigger, + signal.trigger signal.trigger.flag - check .filter and .filter.not untracked by default for signal + check .filter, .filter.not and flow untracked by default for signal + check signal.filter and signal.flow signals passing through === [] signal(0).as.value() From fad524734969efb47065757398bbde719df7eab3 Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 17:07:42 +0700 Subject: [PATCH 042/100] feat: track-untrack test case added for _value with view --- src/index.ts | 1 - tests/_api.test.ts | 32 +++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index e775a27e..39841fad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,7 +102,6 @@ const def_prop = Object.defineProperty; TODOs: [] Tests for all track/untrack functions - [] .view.untrack [] .pre.untrack [] .pre.filter.untrack [] .pre.filter.not.untrack diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 3e1d9174..540f485a 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1,4 +1,4 @@ -import { _value, _selector, _transaction, loop } from '../src'; +import { _value, _selector, _transaction, cycle } from '../src'; test('should work _value with call, get, set, update, sync', () => { const spy = jest.fn(); @@ -834,7 +834,37 @@ test('should work track-untrack for _value with filter.not', () => { expect(u.val).toBe(2); }); +test('should work track-untrack for _value with view', () => { + let t; + const f = _value(0); + const v = _value(0); + + const a = v.view((_v) => _v + f.val); + const b = ((t = v.view.track), t((_v) => _v + f.val)); + const c = ((t = v.view.untrack), t((_v) => _v + f.val)); + + const spy_a = jest.fn(); + const spy_b = jest.fn(); + const spy_c = jest.fn(); + cycle(() => spy_a(a.val)); + cycle(() => spy_b(b.val)); + cycle(() => spy_c(c.val)); + + expect(spy_a).toBeCalledTimes(1); + expect(spy_b).toBeCalledTimes(1); + expect(spy_c).toBeCalledTimes(1); + + f.val = 1; + expect(spy_a).toBeCalledTimes(2); + expect(spy_b).toBeCalledTimes(2); + expect(spy_c).toBeCalledTimes(1); + + v(1); + expect(spy_a).toBeCalledTimes(3); + expect(spy_b).toBeCalledTimes(3); + expect(spy_c).toBeCalledTimes(2); +}); From 183ca77552b69ca27c219ada3736881771a94e3a Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 17:32:58 +0700 Subject: [PATCH 043/100] feat: _value track-untrack test cases added for update, pre, pre.filter, pre.filter.not --- src/index.ts | 27 ++++++------ tests/_api.test.ts | 108 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 39841fad..34a64f72 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,11 +101,6 @@ const def_prop = Object.defineProperty; /* TODOs: [] Tests for - all track/untrack functions - [] .pre.untrack - [] .pre.filter.untrack - [] .pre.filter.not.untrack - etc. signal signal.from signal.trigger @@ -317,11 +312,17 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { return ctx; } +const make_trait_ent_pure_fn_untrack = (trait_fn) => + (ctx, fn) => trait_fn(ctx, (a,b) => { + const finish = internal_untrack(); + try { return fn && fn(a,b) } + finally { finish() } + }); const make_trait_ent_untrack = (trait_fn) => (ctx, fn) => trait_fn(ctx, (a,b) => { const finish = internal_untrack(); - try { return fn[key_get] ? fn[key_get]() : fn(a,b) } + try { return fn && (fn[key_get] ? fn[key_get]() : fn(a,b)) } finally { finish() } }); @@ -351,8 +352,8 @@ const prop_factory_dirty_required_initial = (ctx) => { -const trait_ent_update = (ctx, fn) => (ctx[key_set](fn && fn(ctx[key_get]()))); -const trait_ent_update_untrack = make_trait_ent_untrack(trait_ent_update); +const trait_ent_update = (ctx, fn) => (fn && ctx[key_set](fn(_untrack(ctx[key_get])))); +const trait_ent_update_untrack = make_trait_ent_pure_fn_untrack(trait_ent_update); const trait_ent_update_by = (ctx, src, fn) => { const src_get = src[key_get] ? src[key_get] : src; const e = expr(src_get, fn @@ -421,7 +422,7 @@ const trait_ent_to_once = (ctx, fn) => { const trait_ent_select = (ctx, fn) => ( fill_entity(sel(fn ? () => fn(ctx[key_get]()) : ctx[key_get]).slice(0, 1), proto_entity_readable) ); -const trait_ent_select_untrack = make_trait_ent_untrack(trait_ent_select); +const trait_ent_select_untrack = make_trait_ent_pure_fn_untrack(trait_ent_select); const trait_ent_select_multiple = (ctx, cfg) => obj_keys(cfg).reduce((ret, key) => ( (ret[key] = trait_ent_select(ctx, cfg[key])), ret ), {}); @@ -435,7 +436,7 @@ const trait_ent_view = (ctx, fn) => ( ctx[key_set] && ctx[key_set].bind() ) ); -const trait_ent_view_untrack = make_trait_ent_untrack(trait_ent_view); +const trait_ent_view_untrack = make_trait_ent_pure_fn_untrack(trait_ent_view); const trait_ent_pre = (ctx, fn) => ( fn ? fill_entity(ctx[key_handler], ctx[key_proto], @@ -445,7 +446,7 @@ const trait_ent_pre = (ctx, fn) => ( ) : ctx ); -const trait_ent_pre_untrack = make_trait_ent_untrack(trait_ent_pre); +const trait_ent_pre_untrack = make_trait_ent_pure_fn_untrack(trait_ent_pre); const trait_ent_pre_filter = (ctx, fn) => ( (fn = fn ? (fn[key_get] ? fn[key_get] : fn) @@ -483,7 +484,7 @@ const trait_ent_flow = (ctx, fn) => { h[1] ? proto_entity_writtable : proto_entity_readable ); }; -const trait_ent_flow_untrack = make_trait_ent_untrack(trait_ent_flow); +const trait_ent_flow_untrack = make_trait_ent_pure_fn_untrack(trait_ent_flow); const trait_ent_filter = (ctx, fn) => ( trait_ent_flow(ctx, fn ? (fn[key_get] && (fn = fn[key_get]), @@ -614,7 +615,7 @@ const proto_entity_writtable = obj_create(proto_entity_readable); obj_def_prop_trait_with_ns( proto_entity_writtable, key_update, - trait_ent_update, + trait_ent_update_untrack, proto_entity_writtable_update_ns ); obj_def_prop_trait_with_ns( diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 540f485a..e52d8021 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -866,9 +866,117 @@ test('should work track-untrack for _value with view', () => { expect(spy_c).toBeCalledTimes(2); }); +test('should work track-untrack for _value with pre', () => { + let t; + const f = _value(0); + const v = _value(0); + const a = v.pre((_v) => _v + f.val); + const b = ((t = v.pre.track), t((_v) => _v + f.val)); + const c = ((t = v.pre.untrack), t((_v) => _v + f.val)); + const spy_a = jest.fn(); + const spy_b = jest.fn(); + const spy_c = jest.fn(); + + cycle(() => spy_a(a(1))); + cycle(() => spy_b(b(1))); + cycle(() => spy_c(c(1))); + + expect(spy_a).toBeCalledTimes(1); + expect(spy_b).toBeCalledTimes(1); + expect(spy_c).toBeCalledTimes(1); + f.val = 1; + expect(spy_a).toBeCalledTimes(2); + expect(spy_b).toBeCalledTimes(2); + expect(spy_c).toBeCalledTimes(1); + + v(1); + expect(spy_a).toBeCalledTimes(2); + expect(spy_b).toBeCalledTimes(2); + expect(spy_c).toBeCalledTimes(1); +}); + +test('should work track-untrack for _value with pre.filter', () => { + let t; + const f = _value(0); + const v = _value(0); + + const a = v.pre.filter(f); + const b = ((t = v.pre.filter.track), t(f)); + const c = ((t = v.pre.filter.untrack), t(f)); + + const spy_a = jest.fn(); + const spy_b = jest.fn(); + const spy_c = jest.fn(); + + cycle(() => spy_a(a(1))); + cycle(() => spy_b(b(1))); + cycle(() => spy_c(c(1))); + + expect(spy_a).toBeCalledTimes(1); + expect(spy_b).toBeCalledTimes(1); + expect(spy_c).toBeCalledTimes(1); + + f.val = 1; + expect(spy_a).toBeCalledTimes(2); + expect(spy_b).toBeCalledTimes(2); + expect(spy_c).toBeCalledTimes(1); +}); + +test('should work track-untrack for _value with pre.filter.not', () => { + let t; + const f = _value(0); + const v = _value(0); + + const a = v.pre.filter.not(f); + const b = ((t = v.pre.filter.not.track), t(f)); + const c = ((t = v.pre.filter.not.untrack), t(f)); + + const spy_a = jest.fn(); + const spy_b = jest.fn(); + const spy_c = jest.fn(); + + cycle(() => spy_a(a(1))); + cycle(() => spy_b(b(1))); + cycle(() => spy_c(c(1))); + + expect(spy_a).toBeCalledTimes(1); + expect(spy_b).toBeCalledTimes(1); + expect(spy_c).toBeCalledTimes(1); + + f.val = 1; + expect(spy_a).toBeCalledTimes(2); + expect(spy_b).toBeCalledTimes(2); + expect(spy_c).toBeCalledTimes(1); +}); + +test('should work track-untrack for _value with update', () => { + let t; + const f = _value(0); + + const a = _value(0); + const b = _value(0); + const c = _value(0); + + const spy_a = jest.fn(); + const spy_b = jest.fn(); + const spy_c = jest.fn(); + + cycle(() => spy_a(((t = a.update), t(v => v + f.val)))); + cycle(() => spy_b(((t = b.update.track), t(v => v + f.val)))); + cycle(() => spy_c(((t = c.update.untrack), t(v => v + f.val)))); + + expect(spy_a).toBeCalledTimes(1); + expect(spy_b).toBeCalledTimes(1); + expect(spy_c).toBeCalledTimes(1); + + f.val = 1; + expect(spy_a).toBeCalledTimes(1); + expect(spy_b).toBeCalledTimes(2); + expect(spy_c).toBeCalledTimes(1); +}); From 363b72cf1967ec25ec54d56ceb1fdea5c7902f54 Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 17:44:25 +0700 Subject: [PATCH 044/100] feat: the code minify by resolve reactive box functions to constants --- src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 34a64f72..1dc4bc04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import React, { Context, FC } from 'react'; -import rb, { expr, box, sel, flow, transaction, untrack } from 'reactive-box'; +import rb, { transaction, untrack } from 'reactive-box'; export { _value, @@ -152,6 +152,11 @@ const const_undef = void 0; // Reactive box specific definitions. // +const expr = rb.expr; +const box = rb.box; +const sel = rb.sel; +const flow = rb.flow; + const internal_flow_stop = flow.stop; const internal_untrack = rb.untrack; const internal_transaction = rb.transaction; From 05408d9db719f2067a3be40a96d89e235d5d2817 Mon Sep 17 00:00:00 2001 From: betula Date: Thu, 20 May 2021 21:27:32 +0700 Subject: [PATCH 045/100] chore: todos updated --- src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1dc4bc04..f8b25711 100644 --- a/src/index.ts +++ b/src/index.ts @@ -109,9 +109,10 @@ const def_prop = Object.defineProperty; check signal.filter and signal.flow signals passing through === + [] .select.multiple([a,b,c]) -> .val = [_a,_b,_c]; [] signal(0).as.value() - [] .combine - [] x.combine([a,b,c]) -> [x,a,b,c] + [] combine({ a, b, c }), combine([a,b,c]) + [] x.join([a,b,c]) -> [x,a,b,c] [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) [] ... From e4fb71768cdb7ac714bf842270c14a1df6648229 Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 21 May 2021 09:31:26 +0700 Subject: [PATCH 046/100] feat: the _signal basic support test cases added --- src/index.ts | 20 ++++++++++++-------- tests/_api.test.ts | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index f8b25711..c773ff46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,13 +101,15 @@ const def_prop = Object.defineProperty; /* TODOs: [] Tests for - signal + check .filter, .filter.not and flow untracked by default for signal + check signal.filter and signal.flow signals passing through + signal.from signal.trigger signal.trigger.flag - check .filter, .filter.not and flow untracked by default for signal - check signal.filter and signal.flow signals passing through + === + [] To register realar-node and realar-core package names === [] .select.multiple([a,b,c]) -> .val = [_a,_b,_c]; [] signal(0).as.value() @@ -117,6 +119,7 @@ const def_prop = Object.defineProperty; [] signal.touchable(initial) [] ... [] combine as root level exportable factory function + [] support destruction Backlog [] v.as.readonly() @@ -194,7 +197,7 @@ type SignalFactory = ValueFactory; const pure_fn = function () {}; const pure_arrow_fn_returns_arg = (v) => v; const pure_arrow_fn_returns_not_arg = (v) => !v; -const pure_arrow_fn_returns_true = () => true; +const pure_arrow_fn_returns_undef = (() => {}) as any; const key_proto = "__proto__"; const key_get = "get"; @@ -476,16 +479,17 @@ const trait_ent_pre_filter_not_untrack = make_trait_ent_untrack(trait_ent_pre_fi const trait_ent_flow = (ctx, fn) => { fn || (fn = pure_arrow_fn_returns_arg); let started, prev; + const is_signal = ctx[key_handler][key_is_signal]; const f = flow(() => { const v = ctx[key_get](); try { return fn(v, prev) } finally { prev = v } - }, const_undef, ctx[key_is_signal] && pure_arrow_fn_returns_true); + }, const_undef, is_signal && pure_arrow_fn_returns_undef); const h = [ () => ((started || (f[0](), (started = 1))), f[1]()), ctx[key_set] && ctx[key_set].bind() ]; - h[key_is_signal] = ctx[key_is_signal]; + h[key_is_signal] = is_signal; return fill_entity(h, h[1] ? proto_entity_writtable : proto_entity_readable ); @@ -670,7 +674,7 @@ obj_def_prop_factory( const make_trigger = (initial, has_inverted_to?, is_signal?) => { - const handler = box(initial, () => (handler[key_touched_internal] = 1), is_signal && pure_arrow_fn_returns_true); + const handler = box(initial, () => (handler[key_touched_internal] = 1), is_signal && pure_arrow_fn_returns_undef); const set = has_inverted_to ? () => { handler[key_touched_internal] || handler[1](!handler[0]()) } : (v) => { handler[key_touched_internal] || handler[1](v) }; @@ -703,7 +707,7 @@ const _selector: SelectorFactory = (fn) => ( ) const _signal: SignalFactory = ((initial) => { - const h = box(initial, 0 as any, pure_arrow_fn_returns_true); + const h = box(initial, 0 as any, pure_arrow_fn_returns_undef); h[key_is_signal] = 1; return fill_entity(h, proto_entity_writtable_leaf, 1, initial) }) as any; diff --git a/tests/_api.test.ts b/tests/_api.test.ts index e52d8021..59a24a51 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1,4 +1,4 @@ -import { _value, _selector, _transaction, cycle } from '../src'; +import { _value, _selector, _transaction, cycle, _signal } from '../src'; test('should work _value with call, get, set, update, sync', () => { const spy = jest.fn(); @@ -978,7 +978,49 @@ test('should work track-untrack for _value with update', () => { expect(spy_c).toBeCalledTimes(1); }); +test('should work _signal basic support', () => { + let t, z; + const spy = jest.fn(); + const v = _signal(0); + + (t = v.sync), (t = t(spy)); + expect(spy).toBeCalledWith(0, void 0); spy.mockReset(); + + (z = t.update), z(_v => _v); + expect(spy).toBeCalledWith(0, 0); spy.mockReset(); + (z = t.set), z(0); + expect(spy).toBeCalledWith(0, 0); spy.mockReset(); + expect(t.val).toBe(0); + (z = t.get); + expect(z()).toBe(0); + (z = t.dirty), (z = z.get); + expect(z()).toBe(false); + + const u = ((z = _signal().view), (z = z(_u => _u + 1).pre), (z = z(_u => _u * 10).flow), (z = z(_u => _u * 2))); + (t = t.update), (t = t.by), (t = t(u)); + u(1); + expect(t.val).toBe(22); + expect(t.get()).toBe(22); + expect(spy).toBeCalledWith(22, 0); spy.mockReset(); + u(1); + expect(spy).toBeCalledWith(22, 22); spy.mockReset(); + + expect(v.dirty.val).toBe(true); + (z = t.reset), z(); + expect(v.val).toBe(0); + expect(v.dirty.val).toBe(false); + expect(spy).toBeCalledWith(0, 22); spy.mockReset(); + + v.reset(); + expect(spy).toBeCalledWith(0, 0); spy.mockReset(); + (z = t.reinit), z(5); + expect(spy).toBeCalledWith(5, 0); spy.mockReset(); + expect(v.dirty.val).toBe(false); + u(0); + expect(spy).toBeCalledWith(2, 5); spy.mockReset(); + expect(v.dirty.val).toBe(true); +}); From 3036e0b393a86e48f4c0f18723c7d1169b5f353f Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 21 May 2021 11:03:00 +0700 Subject: [PATCH 047/100] feat: the track-untrack support tested for _signal with .filter, .flow, .filter.not --- src/index.ts | 2 -- tests/_api.test.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index c773ff46..55807507 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,8 +101,6 @@ const def_prop = Object.defineProperty; /* TODOs: [] Tests for - check .filter, .filter.not and flow untracked by default for signal - check signal.filter and signal.flow signals passing through signal.from signal.trigger diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 59a24a51..42f2c04a 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1022,7 +1022,68 @@ test('should work _signal basic support', () => { expect(v.dirty.val).toBe(true); }); +test('should work track-untrack for _signal with flow', () => { + const spy_p = jest.fn(); + const spy_t = jest.fn(); + const spy_u = jest.fn(); + const a = _signal(0); + const v = _signal(0); + v.flow((_v) => _v + a.val).to(spy_p); + v.flow.track((_v) => _v + a.val).to(spy_t); + v.flow.untrack((_v) => _v + a.val).to(spy_u); + + v(0); + expect(spy_p).toBeCalledWith(0, 0); spy_p.mockReset(); + expect(spy_t).toBeCalledWith(0, 0); spy_t.mockReset(); + expect(spy_u).toBeCalledWith(0, 0); spy_u.mockReset(); + a(0); + expect(spy_p).toBeCalledTimes(0); + expect(spy_t).toBeCalledWith(0, 0); spy_t.mockReset(); + expect(spy_u).toBeCalledTimes(0); +}); + +test('should work track-untrack for _signal with filter', () => { + const spy_p = jest.fn(); + const spy_t = jest.fn(); + const spy_u = jest.fn(); + const a = _signal(1); + + const v = _signal(0); + v.filter(a).to(spy_p); + v.filter.track(a).to(spy_t); + v.filter.untrack(a).to(spy_u); + + v(0); + expect(spy_p).toBeCalledWith(0, 0); spy_p.mockReset(); + expect(spy_t).toBeCalledWith(0, 0); spy_t.mockReset(); + expect(spy_u).toBeCalledWith(0, 0); spy_u.mockReset(); + a(1); + expect(spy_p).toBeCalledTimes(0); + expect(spy_t).toBeCalledWith(0, 0); spy_t.mockReset(); + expect(spy_u).toBeCalledTimes(0); +}); + +test('should work track-untrack for _signal with filter.not', () => { + const spy_p = jest.fn(); + const spy_t = jest.fn(); + const spy_u = jest.fn(); + const a = _signal(0); + + const v = _signal(0); + v.filter.not(a).to(spy_p); + v.filter.not.track(a).to(spy_t); + v.filter.not.untrack(a).to(spy_u); + + v(0); + expect(spy_p).toBeCalledWith(0, 0); spy_p.mockReset(); + expect(spy_t).toBeCalledWith(0, 0); spy_t.mockReset(); + expect(spy_u).toBeCalledWith(0, 0); spy_u.mockReset(); + a(0); + expect(spy_p).toBeCalledTimes(0); + expect(spy_t).toBeCalledWith(0, 0); spy_t.mockReset(); + expect(spy_u).toBeCalledTimes(0); +}); From b06af97ddf6b1e230680b91a003e9fd1ac8ea86b Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 21 May 2021 11:18:59 +0700 Subject: [PATCH 048/100] feat: added test cases to _signal.from --- src/index.ts | 3 --- tests/_api.test.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 55807507..0ae28313 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,12 +102,9 @@ const def_prop = Object.defineProperty; TODOs: [] Tests for - signal.from signal.trigger signal.trigger.flag - === - [] To register realar-node and realar-core package names === [] .select.multiple([a,b,c]) -> .val = [_a,_b,_c]; [] signal(0).as.value() diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 42f2c04a..ed24ef72 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1089,6 +1089,73 @@ test('should work track-untrack for _signal with filter.not', () => { +test('should work _signal.from with one argument', () => { + let t; + const spy = jest.fn(); + const a = _signal(0); + const v = _signal.from(() => a.val + 1); + expect(v.val).toBe(1); + (t = v.to), t(spy); + + a.val = 0; + expect(v.val).toBe(1); + expect(spy).toBeCalledWith(1, 1); + + expect(typeof v).toBe('object'); + expect(v.to.once).not.toBeUndefined(); + expect(v.flow).not.toBeUndefined(); + expect(v.filter).not.toBeUndefined(); + expect(v.filter.not).not.toBeUndefined(); + expect(v.view).not.toBeUndefined(); + expect(v.promise).not.toBeUndefined(); + + expect(v.set).toBeUndefined(); + expect(v.update).toBeUndefined(); + expect(v.pre).toBeUndefined(); + expect(v.reset).toBeUndefined(); + expect(v.reinit).toBeUndefined(); + expect(v.dirty).toBeUndefined(); +}); + +test('should work _signal.from with two arguments', () => { + let t; + const spy = jest.fn(); + const u = _signal(0); + const a = _signal(0); + const v = _signal.from(() => a.val + 1, (v) => a(v + v)); + expect(v.val).toBe(1); + (t = v.to), (t = t(spy)); + (t = t.update), (t = t.by), t(() => u.val); + + a.val = 0; + expect(v.val).toBe(1); + expect(spy).toBeCalledWith(1, 1); spy.mockReset(); + + expect(typeof v).toBe('function'); + expect(v.sync).not.toBeUndefined(); + expect(v.to.once).not.toBeUndefined(); + expect(v.flow).not.toBeUndefined(); + expect(v.filter).not.toBeUndefined(); + expect(v.filter.not).not.toBeUndefined(); + expect(v.view).not.toBeUndefined(); + expect(v.set).not.toBeUndefined(); + expect(v.update).not.toBeUndefined(); + expect(v.update.by).not.toBeUndefined(); + expect(v.pre).not.toBeUndefined(); + expect(v.pre.filter).not.toBeUndefined(); + expect(v.pre.filter.not).not.toBeUndefined(); + expect(v.promise).not.toBeUndefined(); + + expect(v.reset).toBeUndefined(); + expect(v.reinit).toBeUndefined(); + expect(v.dirty).toBeUndefined(); + + u(0); + expect(v.val).toBe(1); + expect(spy).toBeCalledWith(1, 1); spy.mockReset(); +}); + + From 1fb2eabfcea619301cc52dfb1aef3b505682ba8c Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 21 May 2021 13:11:04 +0700 Subject: [PATCH 049/100] feat: test cases for _signal.trigger and .trigger.flag added --- src/index.ts | 29 +++++++++------------ tests/_api.test.ts | 63 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0ae28313..a797ccee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,20 +100,14 @@ const def_prop = Object.defineProperty; /* TODOs: - [] Tests for - - signal.trigger - signal.trigger.flag + [] trigger should be touchable + [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds + [] signal.touchable(initial) - === [] .select.multiple([a,b,c]) -> .val = [_a,_b,_c]; [] signal(0).as.value() - [] combine({ a, b, c }), combine([a,b,c]) + [] combine({ a, b, c }), combine([a,b,c]) <- combine as root level exportable factory function [] x.join([a,b,c]) -> [x,a,b,c] - [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds - [] signal.touchable(initial) - [] ... - [] combine as root level exportable factory function [] support destruction Backlog @@ -317,18 +311,18 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { } const make_trait_ent_pure_fn_untrack = (trait_fn) => - (ctx, fn) => trait_fn(ctx, (a,b) => { + (ctx, fn) => trait_fn(ctx, fn && ((a,b) => { const finish = internal_untrack(); - try { return fn && fn(a,b) } + try { return fn(a,b); } finally { finish() } - }); + })); const make_trait_ent_untrack = (trait_fn) => - (ctx, fn) => trait_fn(ctx, (a,b) => { + (ctx, fn) => trait_fn(ctx, fn && ((a,b) => { const finish = internal_untrack(); - try { return fn && (fn[key_get] ? fn[key_get]() : fn(a,b)) } + try { return (fn[key_get] ? fn[key_get]() : fn(a,b)) } finally { finish() } - }); + })); const op_trait_if_signal = (trait_if_not_signal, trait_if_signal) => ( (ctx) => ctx[key_handler][key_is_signal] ? trait_if_signal : trait_if_not_signal @@ -671,9 +665,10 @@ obj_def_prop_factory( const make_trigger = (initial, has_inverted_to?, is_signal?) => { const handler = box(initial, () => (handler[key_touched_internal] = 1), is_signal && pure_arrow_fn_returns_undef); const set = has_inverted_to - ? () => { handler[key_touched_internal] || handler[1](!handler[0]()) } + ? () => { handler[key_touched_internal] || handler[1](!_untrack(handler[0])) } : (v) => { handler[key_touched_internal] || handler[1](v) }; handler[key_reset_promise_by_reset] = 1; + handler[key_is_signal] = is_signal; return fill_entity(handler, proto_entity_writtable_leaf, 1, initial, 0, set); } diff --git a/tests/_api.test.ts b/tests/_api.test.ts index ed24ef72..ebcbb0f3 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1085,10 +1085,6 @@ test('should work track-untrack for _signal with filter.not', () => { expect(spy_u).toBeCalledTimes(0); }); - - - - test('should work _signal.from with one argument', () => { let t; const spy = jest.fn(); @@ -1155,6 +1151,65 @@ test('should work _signal.from with two arguments', () => { expect(spy).toBeCalledWith(1, 1); spy.mockReset(); }); +test('should work _signal.trigger', () => { + const spy_t = jest.fn(); + const spy_f = jest.fn(); + const spy_u = jest.fn(); + + const t = _signal.trigger(); + const f = _signal.trigger.flag(); + const u = _signal.trigger.flag.invert(); + + t.flow().flow().sync(spy_t)(); + f.flow().flow().sync(spy_f)(); + u.flow().flow().sync(spy_u)(); + + expect(spy_t).toHaveBeenNthCalledWith(1, void 0, void 0); + expect(spy_t).toHaveBeenNthCalledWith(2, void 0, void 0); + expect(spy_f).toHaveBeenNthCalledWith(1, false, void 0); + expect(spy_f).toHaveBeenNthCalledWith(2, true, false); + expect(spy_u).toHaveBeenNthCalledWith(1, true, void 0); + expect(spy_u).toHaveBeenNthCalledWith(2, false, true); + + t(); f(); u(); + expect(spy_t).toBeCalledTimes(2); spy_t.mockReset(); + expect(spy_f).toBeCalledTimes(2); spy_f.mockReset(); + expect(spy_u).toBeCalledTimes(2); spy_u.mockReset(); + + expect(t.dirty.val).toBe(false); + expect(f.dirty.val).toBe(true); + expect(u.dirty.val).toBe(true); + + t.reset(); t(); + f.reset(); f(); + u.reset(); u(); + expect(spy_t).toHaveBeenNthCalledWith(1, void 0, void 0); + expect(spy_t).toHaveBeenNthCalledWith(2, void 0, void 0); + expect(spy_f).toHaveBeenNthCalledWith(1, false, true); + expect(spy_f).toHaveBeenNthCalledWith(2, true, false); + expect(spy_u).toHaveBeenNthCalledWith(1, true, false); + expect(spy_u).toHaveBeenNthCalledWith(2, false, true); +}); + + + + + + + + + + + + + + + + + + + + From d5bd945f8fa138660c7c7b15356781aca0cbfe70 Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 21 May 2021 13:21:33 +0700 Subject: [PATCH 050/100] feat: the _value.select.multiple extended to working with arrays not only objects --- src/index.ts | 6 +++--- tests/_api.test.ts | 13 +++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index a797ccee..53241165 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,7 +104,6 @@ const def_prop = Object.defineProperty; [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) - [] .select.multiple([a,b,c]) -> .val = [_a,_b,_c]; [] signal(0).as.value() [] combine({ a, b, c }), combine([a,b,c]) <- combine as root level exportable factory function [] x.join([a,b,c]) -> [x,a,b,c] @@ -138,6 +137,7 @@ const obj_equals = Object.is; const obj_def_prop = Object.defineProperty; const obj_create = Object.create; const obj_keys = Object.keys; +const obj_is_array = Array.isArray; const new_symbol = Symbol; const const_undef = void 0; @@ -423,10 +423,10 @@ const trait_ent_select = (ctx, fn) => ( const trait_ent_select_untrack = make_trait_ent_pure_fn_untrack(trait_ent_select); const trait_ent_select_multiple = (ctx, cfg) => obj_keys(cfg).reduce((ret, key) => ( (ret[key] = trait_ent_select(ctx, cfg[key])), ret -), {}); +), obj_is_array(cfg) ? [] : {}); const trait_ent_select_multiple_untrack = (ctx, cfg) => obj_keys(cfg).reduce((ret, key) => ( (ret[key] = trait_ent_select_untrack(ctx, cfg[key])), ret -), {}); +), obj_is_array(cfg) ? [] : {}); const trait_ent_view = (ctx, fn) => ( fill_entity(ctx[key_handler], ctx[key_proto], 0, 0, diff --git a/tests/_api.test.ts b/tests/_api.test.ts index ebcbb0f3..861b7ae8 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -687,19 +687,28 @@ test('should work _value.select.multiple', () => { a: (_v) => _v + 1, b: (_v) => _v + k.val, }); - const { a, b } = t; + const [ a1, b1 ] = v.select.multiple([ + (_v) => _v + 1, + (_v) => _v + k.val + ]); expect(a.val).toBe(2); expect(b.val).toBe(1); + expect(a1.val).toBe(2); + expect(b1.val).toBe(1); v(2); expect(a.val).toBe(3); expect(b.val).toBe(2); + expect(a1.val).toBe(3); + expect(b1.val).toBe(2); k(2); expect(a.val).toBe(3); expect(b.val).toBe(4); + expect(a1.val).toBe(3); + expect(b1.val).toBe(4); - [a,b].forEach((v) => { + [a,b,a1,b1].forEach((v) => { expect(typeof v).toBe('object'); expect(v.sync).not.toBeUndefined(); expect(v.to).not.toBeUndefined(); From 483d3e29ff2b88e72b835be65e95b57af2d695b3 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 09:54:01 +0700 Subject: [PATCH 051/100] feat: the combine factory added to _value and _signal --- src/index.ts | 62 +++++++++++++++++++++++++++++++--------------- tests/_api.test.ts | 49 +++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/src/index.ts b/src/index.ts index 53241165..a4b63c57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,13 +100,12 @@ const def_prop = Object.defineProperty; /* TODOs: + [] x.join([a,b,c]) -> [x,a,b,c] + [] signal(0).as.value() + [] trigger should be touchable [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) - - [] signal(0).as.value() - [] combine({ a, b, c }), combine([a,b,c]) <- combine as root level exportable factory function - [] x.join([a,b,c]) -> [x,a,b,c] [] support destruction Backlog @@ -164,14 +163,11 @@ type ValueFactory = { (initial?: any): any; flag: { (initial?: any): any; - invert: { - (initial?: any): any; - } + invert: { (initial?: any): any } } }; - from: { - (get: () => any, set?: (v) => any): any; - } + from: { (get: () => any, set?: (v) => any): any }, + combine: { (cfg: any): any } } type SelectorFactory = { (fn: () => any): any; @@ -222,6 +218,7 @@ const key_is_signal = new_symbol(); const key_track = "track"; const key_untrack = "untrack"; const key_multiple = "multiple"; +const key_combine = "combine"; const obj_def_prop_value = (obj, key, value) => ( @@ -353,7 +350,7 @@ const prop_factory_dirty_required_initial = (ctx) => { const trait_ent_update = (ctx, fn) => (fn && ctx[key_set](fn(_untrack(ctx[key_get])))); const trait_ent_update_untrack = make_trait_ent_pure_fn_untrack(trait_ent_update); const trait_ent_update_by = (ctx, src, fn) => { - const src_get = src[key_get] ? src[key_get] : src; + const src_get = src[key_get] || src; const e = expr(src_get, fn ? () => { try { @@ -381,7 +378,7 @@ const trait_ent_reset = (ctx) => { ctx[key_handler][key_touched_internal] = 0; }; const trait_ent_reset_by = (ctx, src) => { - const src_get = src[key_get] ? src[key_get] : src; + const src_get = src[key_get] || src; const e = expr(src_get, () => { trait_ent_reset(ctx); e[0]() @@ -394,7 +391,7 @@ const trait_ent_reinit = (ctx, initial) => { trait_ent_reset(ctx); }; const trait_ent_reinit_by = (ctx, src) => { - const src_get = src[key_get] ? src[key_get] : src; + const src_get = src[key_get] || src; const e = expr(src_get, () => { trait_ent_reinit(ctx, src_get()); e[0](); @@ -447,7 +444,7 @@ const trait_ent_pre = (ctx, fn) => ( const trait_ent_pre_untrack = make_trait_ent_pure_fn_untrack(trait_ent_pre); const trait_ent_pre_filter = (ctx, fn) => ( (fn = fn - ? (fn[key_get] ? fn[key_get] : fn) + ? (fn[key_get] || fn) : pure_arrow_fn_returns_arg ), fill_entity(ctx[key_handler], ctx[key_proto], 0, 0, @@ -674,6 +671,11 @@ const make_trigger = (initial, has_inverted_to?, is_signal?) => { +const _selector: SelectorFactory = (fn) => ( + fill_entity(sel(fn).slice(0, 1), proto_entity_readable) +) + + const _value: ValueFactory = ((initial) => ( fill_entity(box(initial), proto_entity_writtable_leaf, 1, initial) )) as any; @@ -682,20 +684,27 @@ const value_trigger = (initial) => make_trigger(initial); const value_trigger_flag = (initial) => make_trigger(!!initial, 1); const value_trigger_flag_invert = (initial) => make_trigger(!initial, 1); const value_from = (get, set?) => ( - (get = sel(get).slice(0, 1), set && (get[1] = set)), + (get = sel(get).slice(0, 1), set && (get[1] = set.bind())), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) ); +const value_combine = (cfg) => { + const keys = obj_keys(cfg); + const fns = keys.map((key) => sel(cfg[key][key_get] || cfg[key])[0]); + const is_array = obj_is_array(cfg); + return fill_entity([ + () => keys.reduce((ret, key, key_index) => ( + (ret[key] = fns[key_index]()), ret + ), is_array ? [] : {}) + ], proto_entity_readable) +}; value_trigger_flag[key_invert] = value_trigger_flag_invert; value_trigger[key_flag] = value_trigger_flag; _value[key_trigger] = value_trigger as any; _value[key_from] = value_from; +_value[key_combine] = value_combine; -const _selector: SelectorFactory = (fn) => ( - fill_entity(sel(fn).slice(0, 1), proto_entity_readable) -) - const _signal: SignalFactory = ((initial) => { const h = box(initial, 0 as any, pure_arrow_fn_returns_undef); h[key_is_signal] = 1; @@ -706,14 +715,27 @@ const signal_trigger = (initial) => make_trigger(initial, 0, 1); const signal_trigger_flag = (initial) => make_trigger(!!initial, 1, 1); const signal_trigger_flag_invert = (initial) => make_trigger(!initial, 1, 1); const _signal_from = (get, set?) => ( - (get = [get], (get[key_is_signal] = 1), set && (get[1] = set)), + (get = [get], (get[key_is_signal] = 1), set && (get[1] = set.bind())), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) ); +const _signal_combine = (cfg) => { + const keys = obj_keys(cfg); + const fns = keys.map((key) => cfg[key][key_get] || cfg[key]); + const is_array = obj_is_array(cfg); + const h = [ + () => keys.reduce((ret, key, key_index) => ( + (ret[key] = fns[key_index]()), ret + ), is_array ? [] : {}) + ]; + h[key_is_signal] = 1; + return fill_entity(h, proto_entity_readable) +}; signal_trigger_flag[key_invert] = signal_trigger_flag_invert; signal_trigger[key_flag] = signal_trigger_flag; _signal[key_trigger] = signal_trigger as any; _signal[key_from] = _signal_from; +_signal[key_combine] = _signal_combine; // diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 861b7ae8..13ac5721 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1200,10 +1200,57 @@ test('should work _signal.trigger', () => { expect(spy_u).toHaveBeenNthCalledWith(2, false, true); }); +test('should work value.combine', () => { + const spy_v = jest.fn(); + const spy_w = jest.fn(); + const a = _signal(0); + _value.combine({ + a: a, + b: a.flow(_a => _a + 1), + c: () => a.val + }).sync(spy_v); + _value.combine([ + a, + a.flow(_a => _a + 1), + () => a.val + ]).sync(spy_w); + + expect(spy_v).toBeCalledWith({ a: 0, b: 1, c: 0 }, void 0); spy_v.mockReset(); + expect(spy_w).toBeCalledWith([ 0, 1, 0 ], void 0); spy_w.mockReset(); + a(0); + expect(spy_v).toBeCalledTimes(0); + expect(spy_w).toBeCalledTimes(0); + a(1); + expect(spy_v).toBeCalledWith({ a: 1, b: 2, c: 1 }, { a: 0, b: 1, c: 0 }); + expect(spy_w).toBeCalledWith([ 1, 2, 1 ], [ 0, 1, 0 ]); +}); +test('should work _signal.combine', () => { + const spy_v = jest.fn(); + const spy_w = jest.fn(); + const a = _signal(0); - + _signal.combine({ + a: a, + b: a.flow(_a => _a + 1), + c: () => a.val + }).sync(spy_v); + _signal.combine([ + a, + a.flow(_a => _a + 1), + () => a.val + ]).sync(spy_w); + + expect(spy_v).toBeCalledWith({ a: 0, b: 1, c: 0 }, void 0); spy_v.mockReset(); + expect(spy_w).toBeCalledWith([ 0, 1, 0 ], void 0); spy_w.mockReset(); + a(0); + expect(spy_v).toBeCalledWith({ a: 0, b: 1, c: 0 }, { a: 0, b: 1, c: 0 }); + expect(spy_w).toBeCalledWith([ 0, 1, 0 ], [ 0, 1, 0 ]); + a(1); + expect(spy_v).toBeCalledWith({ a: 1, b: 2, c: 1 }, { a: 0, b: 1, c: 0 }); + expect(spy_w).toBeCalledWith([ 1, 2, 1 ], [ 0, 1, 0 ]); +}); From 773645bcaf3caa6c295d6b55eead15ff62059ff0 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 11:08:02 +0700 Subject: [PATCH 052/100] feat: the join implementation added to _value and _signal --- src/index.ts | 80 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/src/index.ts b/src/index.ts index a4b63c57..43374517 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,7 +100,7 @@ const def_prop = Object.defineProperty; /* TODOs: - [] x.join([a,b,c]) -> [x,a,b,c] + [] test cases for join [] signal(0).as.value() [] trigger should be touchable @@ -219,6 +219,7 @@ const key_track = "track"; const key_untrack = "untrack"; const key_multiple = "multiple"; const key_combine = "combine"; +const key_join = "join"; const obj_def_prop_value = (obj, key, value) => ( @@ -307,6 +308,24 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { return ctx; } +const make_join_entity = (fn_get, join_cfg, is_signal?, set?, is_untrack?) => { + const fns = join_cfg.map(is_signal + ? (key) => join_cfg[key][key_get] || join_cfg[key] + : (key) => sel(join_cfg[key][key_get] || join_cfg[key])[0] + ); + const h = [ + () => { + const ret = [fn_get()]; + const finish = is_untrack && internal_untrack(); + try { return ret.concat(fns.map(f => f())) } + finally { finish && finish() } + }, + set && set.bind() + ]; + h[key_is_signal] = is_signal; + return fill_entity(h, set ? proto_entity_writtable : proto_entity_readable) +} + const make_trait_ent_pure_fn_untrack = (trait_fn) => (ctx, fn) => trait_fn(ctx, fn && ((a,b) => { const finish = internal_untrack(); @@ -498,6 +517,14 @@ const trait_ent_filter_not = (ctx, fn) => ( ); const trait_ent_filter_not_untrack = make_trait_ent_untrack(trait_ent_filter_not) +const trait_ent_join = (ctx, cfg) => ( + make_join_entity(ctx[key_get], cfg, ctx[key_handler][key_is_signal], ctx[key_set]) +); +const trait_ent_join_untrack = (ctx, cfg) => ( + make_join_entity(ctx[key_get], cfg, ctx[key_handler][key_is_signal], ctx[key_set], 1) +); + + // readable.to:ns @@ -534,8 +561,9 @@ obj_def_prop_trait_ns_with_ns(proto_entity_readable_select_ns, key_multiple, tra // .filter.not (track|untrack) // .select:readable.select:ns (track|untrack) // .select.multiple (track|untrack) -// .view (track|untrack) // .flow (track|untrack) +// .view (track|untrack) +// .join (track|untrack) // .promise const proto_entity_readable = obj_create(pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); @@ -569,7 +597,13 @@ obj_def_prop_trait_with_ns( proto_entity_readable, key_view, trait_ent_view, - make_proto_for_trackable_ns(trait_ent_select, trait_ent_view_untrack), + make_proto_for_trackable_ns(trait_ent_view, trait_ent_view_untrack), +); +obj_def_prop_trait_with_ns( + proto_entity_readable, + key_join, + trait_ent_join, + make_proto_for_trackable_ns(trait_ent_join, trait_ent_join_untrack), ); obj_def_prop_promise(proto_entity_readable); @@ -669,6 +703,22 @@ const make_trigger = (initial, has_inverted_to?, is_signal?) => { return fill_entity(handler, proto_entity_writtable_leaf, 1, initial, 0, set); } +const make_combine = (cfg, is_signal?) => { + const keys = obj_keys(cfg); + const fns = keys.map(is_signal + ? (key) => cfg[key][key_get] || cfg[key] + : (key) => sel(cfg[key][key_get] || cfg[key])[0] + ); + const is_array = obj_is_array(cfg); + const h = [ + () => keys.reduce((ret, key, key_index) => ( + (ret[key] = fns[key_index]()), ret + ), is_array ? [] : {}) + ]; + h[key_is_signal] = is_signal; + return fill_entity(h, proto_entity_readable); +} + const _selector: SelectorFactory = (fn) => ( @@ -687,16 +737,7 @@ const value_from = (get, set?) => ( (get = sel(get).slice(0, 1), set && (get[1] = set.bind())), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) ); -const value_combine = (cfg) => { - const keys = obj_keys(cfg); - const fns = keys.map((key) => sel(cfg[key][key_get] || cfg[key])[0]); - const is_array = obj_is_array(cfg); - return fill_entity([ - () => keys.reduce((ret, key, key_index) => ( - (ret[key] = fns[key_index]()), ret - ), is_array ? [] : {}) - ], proto_entity_readable) -}; +const value_combine = (cfg) => make_combine(cfg); value_trigger_flag[key_invert] = value_trigger_flag_invert; value_trigger[key_flag] = value_trigger_flag; @@ -718,18 +759,7 @@ const _signal_from = (get, set?) => ( (get = [get], (get[key_is_signal] = 1), set && (get[1] = set.bind())), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) ); -const _signal_combine = (cfg) => { - const keys = obj_keys(cfg); - const fns = keys.map((key) => cfg[key][key_get] || cfg[key]); - const is_array = obj_is_array(cfg); - const h = [ - () => keys.reduce((ret, key, key_index) => ( - (ret[key] = fns[key_index]()), ret - ), is_array ? [] : {}) - ]; - h[key_is_signal] = 1; - return fill_entity(h, proto_entity_readable) -}; +const _signal_combine = (cfg) => make_combine(cfg, 1); signal_trigger_flag[key_invert] = signal_trigger_flag_invert; signal_trigger[key_flag] = signal_trigger_flag; From 3187b32a170056933f46905afa590eaec686fe55 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 11:38:56 +0700 Subject: [PATCH 053/100] feat: test cases added for signal.join and value.join --- src/index.ts | 6 +++--- tests/_api.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 43374517..d365cf18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,7 +100,7 @@ const def_prop = Object.defineProperty; /* TODOs: - [] test cases for join + [] test cases for track-untrack join [] signal(0).as.value() [] trigger should be touchable @@ -310,8 +310,8 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { const make_join_entity = (fn_get, join_cfg, is_signal?, set?, is_untrack?) => { const fns = join_cfg.map(is_signal - ? (key) => join_cfg[key][key_get] || join_cfg[key] - : (key) => sel(join_cfg[key][key_get] || join_cfg[key])[0] + ? (fn) => fn[key_get] || fn + : (fn) => sel(fn[key_get] || fn)[0] ); const h = [ () => { diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 13ac5721..9165ae97 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1252,7 +1252,49 @@ test('should work _signal.combine', () => { expect(spy_w).toBeCalledWith([ 1, 2, 1 ], [ 0, 1, 0 ]); }); +test('should work value.join', () => { + const spy = jest.fn(); + const a = _signal(0); + const z = _value(0); + + z.join([ + a, + a.flow(_a => _a + 1), + () => a.val + ]).sync(spy); + + expect(spy).toBeCalledWith([ 0, 0, 1, 0 ], void 0); spy.mockReset(); + a(0); + expect(spy).toBeCalledTimes(0); + a(1); + expect(spy).toBeCalledWith([ 0, 1, 2, 1 ], [ 0, 0, 1, 0 ]); spy.mockReset(); + z(0); + expect(spy).toBeCalledTimes(0); + z(1); + expect(spy).toBeCalledWith([ 1, 1, 2, 1 ], [ 0, 1, 2, 1 ]); +}); + +test('should work signal.join', () => { + const spy = jest.fn(); + const a = _signal(0); + const z = _signal(0); + z.join([ + a, + a.flow(_a => _a + 1), + () => a.val + ]).sync(spy); + + expect(spy).toBeCalledWith([ 0, 0, 1, 0 ], void 0); spy.mockReset(); + a(0); + expect(spy).toBeCalledWith([ 0, 0, 1, 0 ], [ 0, 0, 1, 0 ]); spy.mockReset(); + a(1); + expect(spy).toBeCalledWith([ 0, 1, 2, 1 ], [ 0, 0, 1, 0 ]); spy.mockReset(); + z(0); + expect(spy).toBeCalledWith([ 0, 1, 2, 1 ], [ 0, 1, 2, 1 ]); spy.mockReset(); + z(1); + expect(spy).toBeCalledWith([ 1, 1, 2, 1 ], [ 0, 1, 2, 1 ]); +}); From fad693f921c4164f1b2cf18bb9e59473936781b2 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 13:32:27 +0700 Subject: [PATCH 054/100] feat: test cases for join to value and signal added --- tests/_api.test.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/_api.test.ts b/tests/_api.test.ts index 9165ae97..e4df3ba8 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1296,11 +1296,57 @@ test('should work signal.join', () => { expect(spy).toBeCalledWith([ 1, 1, 2, 1 ], [ 0, 1, 2, 1 ]); }); +test('should work track-untrack for _value with join', () => { + const spy_p = jest.fn(); + const spy_t = jest.fn(); + const spy_u = jest.fn(); + const a = _signal(0); + const v = _value(0); + v.join([a]).sync(spy_p); + v.join.track([a]).sync(spy_t); + v.join.untrack([a]).sync(spy_u); + expect(spy_p).toBeCalledWith([0,0], void 0); spy_p.mockReset(); + expect(spy_t).toBeCalledWith([0,0], void 0); spy_t.mockReset(); + expect(spy_u).toBeCalledWith([0,0], void 0); spy_u.mockReset(); + v(1); + expect(spy_p).toBeCalledWith([1,0], [0,0]); spy_p.mockReset(); + expect(spy_t).toBeCalledWith([1,0], [0,0]); spy_t.mockReset(); + expect(spy_u).toBeCalledWith([1,0], [0,0]); spy_u.mockReset(); + a(0); + expect(spy_p).toBeCalledTimes(0); + expect(spy_t).toBeCalledTimes(0); + expect(spy_u).toBeCalledTimes(0); + a(1); + expect(spy_p).toBeCalledWith([1,1], [1,0]); spy_p.mockReset(); + expect(spy_t).toBeCalledWith([1,1], [1,0]); spy_t.mockReset(); + expect(spy_u).toBeCalledTimes(0); +}); +test('should work track-untrack for _signal with join', () => { + const spy_p = jest.fn(); + const spy_t = jest.fn(); + const spy_u = jest.fn(); + const a = _signal(0); + const v = _signal(0); + v.join([a]).sync(spy_p); + v.join.track([a]).sync(spy_t); + v.join.untrack([a]).sync(spy_u); + expect(spy_p).toBeCalledWith([0,0], void 0); spy_p.mockReset(); + expect(spy_t).toBeCalledWith([0,0], void 0); spy_t.mockReset(); + expect(spy_u).toBeCalledWith([0,0], void 0); spy_u.mockReset(); + v(0); + expect(spy_p).toBeCalledWith([0,0], [0,0]); spy_p.mockReset(); + expect(spy_t).toBeCalledWith([0,0], [0,0]); spy_t.mockReset(); + expect(spy_u).toBeCalledWith([0,0], [0,0]); spy_u.mockReset(); + a(0); + expect(spy_p).toBeCalledWith([0,0], [0,0]); spy_p.mockReset(); + expect(spy_t).toBeCalledWith([0,0], [0,0]); spy_t.mockReset(); + expect(spy_u).toBeCalledTimes(0); +}); From bc615ad2107aa9162255fdefe1abd5bb5333b640 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 14:00:19 +0700 Subject: [PATCH 055/100] feat: .op and .as.value implemented and tested --- src/index.ts | 41 +++++++++++++++++++++++++++++++++++------ tests/_api.test.ts | 16 +++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index d365cf18..5352b1ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,9 +100,6 @@ const def_prop = Object.defineProperty; /* TODOs: - [] test cases for track-untrack join - [] signal(0).as.value() - [] trigger should be touchable [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) @@ -220,7 +217,9 @@ const key_untrack = "untrack"; const key_multiple = "multiple"; const key_combine = "combine"; const key_join = "join"; - +const key_value = "value"; +const key_as = "as"; +const key_op = "op"; const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -233,6 +232,16 @@ const obj_def_prop_trait = (obj, key, trait) => } }); +const obj_def_prop_with_ns = (obj, key, ns) => + obj_def_prop(obj, key, { + get() { + const ret = {}; + ret[key_proto] = ns; + ret[key_ctx] = this; + return obj_def_prop_value(this, key, ret); + } + }); + const obj_def_prop_trait_ns = (obj, key, trait) => obj_def_prop(obj, key, { get() { @@ -524,6 +533,13 @@ const trait_ent_join_untrack = (ctx, cfg) => ( make_join_entity(ctx[key_get], cfg, ctx[key_handler][key_is_signal], ctx[key_set], 1) ); +const trait_ent_as_value = (ctx) => ( + value_from(ctx[key_get], ctx[key_set]) +); +const trait_ent_op = (ctx, f) => ( + (f = f(ctx)), (f === const_undef ? ctx : f) +); + @@ -552,21 +568,29 @@ obj_def_prop_trait_ns_with_ns(proto_entity_readable_select_ns, key_multiple, tra make_proto_for_trackable_ns(trait_ent_select_multiple, trait_ent_select_multiple_untrack) ); +// readable.as:ns +// .as.value +const proto_entity_readable_as_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_readable_as_ns, key_value, trait_ent_as_value); // readable // .sync +// .op // .to:readable.to:ns // .to.once // .filter:readable.filter:ns (track|untrack) // .filter.not (track|untrack) // .select:readable.select:ns (track|untrack) -// .select.multiple (track|untrack) +// .select.multiple (track|untrack) // .flow (track|untrack) // .view (track|untrack) // .join (track|untrack) +// .as:readable.as:ns +// .as.value // .promise const proto_entity_readable = obj_create(pure_fn); obj_def_prop_trait(proto_entity_readable, key_sync, trait_ent_sync); +obj_def_prop_trait(proto_entity_readable, key_op, trait_ent_op); obj_def_prop_trait_with_ns( proto_entity_readable, key_to, @@ -605,6 +629,11 @@ obj_def_prop_trait_with_ns( trait_ent_join, make_proto_for_trackable_ns(trait_ent_join, trait_ent_join_untrack), ); +obj_def_prop_with_ns( + proto_entity_readable, + key_as, + proto_entity_readable_as_ns +); obj_def_prop_promise(proto_entity_readable); // writtable.update:ns (track|untrack) @@ -640,7 +669,7 @@ obj_def_prop_trait_ns_with_ns( // .update.by // .pre:writtable.pre:ns (track|untrack) // .pre.filter:writtable.pre.filter:ns (track|untrack) -// pre.filter.not (track|untrack) +// .pre.filter.not (track|untrack) const proto_entity_writtable = obj_create(proto_entity_readable); obj_def_prop_trait_with_ns( proto_entity_writtable, diff --git a/tests/_api.test.ts b/tests/_api.test.ts index e4df3ba8..6ef90355 100644 --- a/tests/_api.test.ts +++ b/tests/_api.test.ts @@ -1348,9 +1348,23 @@ test('should work track-untrack for _signal with join', () => { expect(spy_u).toBeCalledTimes(0); }); +test('should work _signal with as.value', () => { + const spy_s = jest.fn(); + const spy_v = jest.fn(); + const s = _signal(0).sync(spy_s).op(s => { + s.as.value().sync(spy_v); + }); - + expect(spy_s).toBeCalledWith(0, void 0); spy_s.mockReset(); + expect(spy_v).toBeCalledWith(0, void 0); spy_v.mockReset(); + s(1); + expect(spy_s).toBeCalledWith(1, 0); spy_s.mockReset(); + expect(spy_v).toBeCalledWith(1, 0); spy_v.mockReset(); + s(1); + expect(spy_s).toBeCalledWith(1, 1); spy_s.mockReset(); + expect(spy_v).toBeCalledTimes(0); +}); From 281884c1edeb019257878e9306076bd8462a5114 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 14:05:31 +0700 Subject: [PATCH 056/100] chore: todos updated --- src/index.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5352b1ce..29261d62 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,21 +104,6 @@ const def_prop = Object.defineProperty; [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds [] signal.touchable(initial) [] support destruction - - Backlog - [] v.as.readonly() - [] flow.resolve - [] flow as root level exportable factory function - [] .chan - [] value.trigger.from - [] value.trigger.flag.from - [] .map <- sysnonym for .view (on thinking) - [] x.group -- x.op -- x.block - x.block((ctx) => ({ // if returns non undefined - a: ctx.a, - b: ctx.a.select() - })).b.val - [] v.as.signal(), v.as.trigger.flag() */ From b7d6ff0f0bd0ebd4405d83e840b67aa94ec9423b Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 14:23:21 +0700 Subject: [PATCH 057/100] chore: todos updated --- src/index.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 29261d62..3b128def 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,9 +100,13 @@ const def_prop = Object.defineProperty; /* TODOs: - [] trigger should be touchable - [] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds - [] signal.touchable(initial) + [] to remove effect + [] to remove loop + + [] rename _value to value + [] rename _signal to signal + [] rename _selector to selector + [] support destruction */ @@ -382,7 +386,7 @@ const trait_ent_sync = (ctx, fn) => { finally { prev_value = e[0](); } }; const e = expr(ctx[key_get], sync); - sync(); + _untrack(sync); return ctx; }; const trait_ent_reset = (ctx) => { @@ -1380,10 +1384,9 @@ function effect(fn: any) { return un(fn()); } -function un(unsub: () => void): () => void { - if (unsub && context_unsubs) context_unsubs.push(unsub); - return unsub; -} +const un = (unsub: () => void): () => void => ( + (unsub && context_unsubs && context_unsubs.push(unsub)), unsub +) function cycle(body: () => void) { const iter = () => { @@ -1584,20 +1587,20 @@ function free() { } } -function box_property(o: any, p: string | number | symbol, init?: any): any { +const obj_def_box_prop = (o: any, p: string | number | symbol, init?: any): any => { const b = box(init && init()); - def_prop(o, p, { get: b[0], set: b[1] }); + obj_def_prop(o, p, { get: b[0], set: b[1] }); } function prop(_proto: any, key: any, descriptor?: any): any { const initializer = descriptor?.initializer; return { get() { - box_property(this, key, initializer); + obj_def_box_prop(this, key, initializer); return this[key]; }, set(value: any) { - box_property(this, key, initializer); + obj_def_box_prop(this, key, initializer); this[key] = value; }, }; @@ -1607,7 +1610,7 @@ function cache(_proto: any, key: any, descriptor: any): any { return { get() { const [get] = sel(descriptor.get); - def_prop(this, key, { get }); + obj_def_prop(this, key, { get }); return this[key]; }, }; From e4ede217140ebb00d39f136a87c436666e12061d Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 16:29:02 +0700 Subject: [PATCH 058/100] feat: the progress for migration to api 6 --- src/index.ts | 104 +++++++------ ...ilter.test.ts => builder-5-filter.test.ts} | 22 +-- ...tor.test.ts => builder-5-selector.test.ts} | 16 +- ...ignal.test.ts => builder-5-signal.test.ts} | 53 +++---- tests/builder-5-trigger.test.ts | 144 ++++++++++++++++++ tests/{_api.test.ts => builder-6.test.ts} | 7 - tests/isolate.test.ts | 96 ++++++------ tests/ready.test.ts | 143 ----------------- tests/shared.test.ts | 4 +- tests/use-local.test.tsx | 4 +- tests/use-scoped.test.tsx | 8 +- 11 files changed, 298 insertions(+), 303 deletions(-) rename tests/{filter.test.ts => builder-5-filter.test.ts} (53%) rename tests/{selector.test.ts => builder-5-selector.test.ts} (58%) rename tests/{signal.test.ts => builder-5-signal.test.ts} (70%) create mode 100644 tests/builder-5-trigger.test.ts rename tests/{_api.test.ts => builder-6.test.ts} (99%) delete mode 100644 tests/ready.test.ts diff --git a/src/index.ts b/src/index.ts index 3b128def..6b97958a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,8 @@ export { _transaction, _signal, _untrack, + _on, + _sync, value, selector, @@ -15,7 +17,6 @@ export { signal, ready, on, - effect, un, hook, sync, @@ -100,14 +101,19 @@ const def_prop = Object.defineProperty; /* TODOs: - [] to remove effect + [] test case "should work signal.trigger with configured .pre" + [] test cases for "isolate" (remove effect) + + [] signal.trigger.from + [] value.trigger.from + [] signal.trigger.resolved + [] value.trigger.resolved + [] to remove loop [] rename _value to value [] rename _signal to signal [] rename _selector to selector - - [] support destruction */ @@ -139,6 +145,10 @@ const internal_flow_stop = flow.stop; const internal_untrack = rb.untrack; const internal_transaction = rb.transaction; +const un_expr = (a,b) => ((a = expr(a,b)), un(a[1]), a); +const un_flow = (a,b,c) => ((a = flow(a,b,c)), un(a[2]), a); + + // // Entity builder for value, signal and etc. Typings. // @@ -158,7 +168,7 @@ type ValueFactory = { type SelectorFactory = { (fn: () => any): any; } -type SignalFactory = ValueFactory; +type SignalFactory = ValueFactory; // @@ -306,6 +316,17 @@ const fill_entity = (handler, proto, has_initial?, initial?, _get?, _set?) => { return ctx; } +const reactionable_subscribe = (target, fn, is_once?, is_sync?) => { + target = target[key_get] || sel(target)[0]; + let value: any; + const e = un_expr(target, () => { + const prev = value; + fn(is_once ? target() : (value = e[0]()), prev); + }); + value = e[0](); + if (is_sync) _untrack(fn.bind(const_undef, value, const_undef)); +} + const make_join_entity = (fn_get, join_cfg, is_signal?, set?, is_untrack?) => { const fns = join_cfg.map(is_signal ? (fn) => fn[key_get] || fn @@ -348,7 +369,6 @@ const make_proto_for_trackable_ns = (trait_track, trait_untrack) => ( key_untrack, trait_untrack) ); - const prop_factory_dirty_required_initial = (ctx) => { const h = ctx[key_handler]; if (!h[key_dirty_handler]) { @@ -368,7 +388,7 @@ const trait_ent_update = (ctx, fn) => (fn && ctx[key_set](fn(_untrack(ctx[key_ge const trait_ent_update_untrack = make_trait_ent_pure_fn_untrack(trait_ent_update); const trait_ent_update_by = (ctx, src, fn) => { const src_get = src[key_get] || src; - const e = expr(src_get, fn + const e = un_expr(src_get, fn ? () => { try { ctx[key_set](fn(ctx[key_get](), src_get(), prev_value)); @@ -379,16 +399,7 @@ const trait_ent_update_by = (ctx, src, fn) => { let prev_value = e[0](); return ctx; }; -const trait_ent_sync = (ctx, fn) => { - let prev_value; - const sync = () => { - try { fn(ctx[key_get](), prev_value); } - finally { prev_value = e[0](); } - }; - const e = expr(ctx[key_get], sync); - _untrack(sync); - return ctx; -}; +const trait_ent_sync = (ctx, fn) => (reactionable_subscribe(ctx, fn, 0, 1), ctx); const trait_ent_reset = (ctx) => { ctx[key_promise_internal] = 0; ctx[key_handler][1](ctx[key_handler][key_initial]); @@ -396,7 +407,7 @@ const trait_ent_reset = (ctx) => { }; const trait_ent_reset_by = (ctx, src) => { const src_get = src[key_get] || src; - const e = expr(src_get, () => { + const e = un_expr(src_get, () => { trait_ent_reset(ctx); e[0]() }); @@ -409,28 +420,15 @@ const trait_ent_reinit = (ctx, initial) => { }; const trait_ent_reinit_by = (ctx, src) => { const src_get = src[key_get] || src; - const e = expr(src_get, () => { + const e = un_expr(src_get, () => { trait_ent_reinit(ctx, src_get()); e[0](); }); e[0](); return ctx; }; -const trait_ent_to = (ctx, fn) => { - let prev_value; - const e = expr(ctx[key_get], () => { - try { fn(ctx[key_get](), prev_value); } - finally { prev_value = e[0](); } - }); - prev_value = e[0](); - return ctx; -}; -const trait_ent_to_once = (ctx, fn) => { - let prev_value; - const e = expr(ctx[key_get], () => fn(ctx[key_get](), prev_value)); - prev_value = e[0](); - return ctx; -}; +const trait_ent_to = (ctx, fn) => (reactionable_subscribe(ctx, fn), ctx); +const trait_ent_to_once = (ctx, fn) => (reactionable_subscribe(ctx, fn, 1), ctx); const trait_ent_select = (ctx, fn) => ( fill_entity(sel(fn ? () => fn(ctx[key_get]()) : ctx[key_get]).slice(0, 1), proto_entity_readable) ); @@ -483,7 +481,7 @@ const trait_ent_flow = (ctx, fn) => { fn || (fn = pure_arrow_fn_returns_arg); let started, prev; const is_signal = ctx[key_handler][key_is_signal]; - const f = flow(() => { + const f = un_flow(() => { const v = ctx[key_get](); try { return fn(v, prev) } finally { prev = v } @@ -752,7 +750,8 @@ const value_trigger = (initial) => make_trigger(initial); const value_trigger_flag = (initial) => make_trigger(!!initial, 1); const value_trigger_flag_invert = (initial) => make_trigger(!initial, 1); const value_from = (get, set?) => ( - (get = sel(get).slice(0, 1), set && (get[1] = set.bind())), + (get = sel(get[key_get] || get).slice(0, 1), + set && ((set = set[key_set] || set), (get[1] = set.bind()))), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) ); const value_combine = (cfg) => make_combine(cfg); @@ -774,7 +773,9 @@ const signal_trigger = (initial) => make_trigger(initial, 0, 1); const signal_trigger_flag = (initial) => make_trigger(!!initial, 1, 1); const signal_trigger_flag_invert = (initial) => make_trigger(!initial, 1, 1); const _signal_from = (get, set?) => ( - (get = [get], (get[key_is_signal] = 1), set && (get[1] = set.bind())), + (get = [get[key_get] || get], + (get[key_is_signal] = 1), + set && ((set = set[key_set] || set), (get[1] = set.bind()))), fill_entity(get, set ? proto_entity_writtable : proto_entity_readable) ); const _signal_combine = (cfg) => make_combine(cfg, 1); @@ -804,7 +805,20 @@ const _untrack = (fn) => { +// +// Realar exportable api +// + +const un = (unsub: () => void) => { + unsub && context_unsubs && context_unsubs.push(unsub) +} +const _on_once = (target, fn) => reactionable_subscribe(target, fn, 1); +const _on = (target, fn) => reactionable_subscribe(target, fn); + +_on[key_once] = _on_once; + +const _sync = (target, fn) => reactionable_subscribe(target, fn, 0, 1); @@ -1378,15 +1392,6 @@ function sync(target: Reactionable, listener: (value: T, prev: T) => void) return on(target, listener); } -function effect(fn: () => void): () => void; -function effect(fn: () => () => any): () => any; -function effect(fn: any) { - return un(fn()); -} - -const un = (unsub: () => void): () => void => ( - (unsub && context_unsubs && context_unsubs.push(unsub)), unsub -) function cycle(body: () => void) { const iter = () => { @@ -1423,6 +1428,13 @@ function isolate(fn?: any) { }; } + + + + + + + function initial(data: any): void { initial_data = data; } diff --git a/tests/filter.test.ts b/tests/builder-5-filter.test.ts similarity index 53% rename from tests/filter.test.ts rename to tests/builder-5-filter.test.ts index e2ef5f83..d0a0d07c 100644 --- a/tests/filter.test.ts +++ b/tests/builder-5-filter.test.ts @@ -1,14 +1,14 @@ -import { signal, on } from '../src'; +import { _signal, _on } from '../src'; -test('should work basic operations with wrap filter method', async () => { +test('should work basic operations with pre.filter method', async () => { const spy = jest.fn(); - const a = signal(0); - const b = signal(0); + const a = _signal(0); + const b = _signal(0); - const c = a.wrap.filter(() => b.val); + const c = a.pre.filter(() => b.val); - on(c, spy); + _on(c, spy); c(1); b(1); @@ -22,15 +22,15 @@ test('should work basic operations with wrap filter method', async () => { expect(spy).toHaveBeenNthCalledWith(2, 3, 2); }); -test('should work basic operations with flow filter method', async () => { +test('should work basic operations with filter method', async () => { const spy = jest.fn(); - const a = signal(0); - const b = signal(0); + const a = _signal(0); + const b = _signal(0); - const c = a.flow.filter(() => b.val); + const c = a.filter(() => b.val); - on(c, spy); + _on(c, spy); a(1); b(1); diff --git a/tests/selector.test.ts b/tests/builder-5-selector.test.ts similarity index 58% rename from tests/selector.test.ts rename to tests/builder-5-selector.test.ts index ea072077..ee002868 100644 --- a/tests/selector.test.ts +++ b/tests/builder-5-selector.test.ts @@ -1,15 +1,14 @@ -import { sync, value, selector } from '../src'; +import { _sync, _value, _selector } from '../src'; test('should work basic operations with selector', () => { const spy_1 = jest.fn(); const spy_2 = jest.fn(); - const a = value(7); - const s = selector(() => (spy_1(a.val), a.val)); + const a = _value(7); + const s = _selector(() => (spy_1(a.val), a.val)); - const [get] = s; - const { free } = s; + const {get} = s; - sync(s, v => spy_2(v)); + _sync(s, v => spy_2(v)); expect(spy_1).toHaveBeenNthCalledWith(1, 7); expect(spy_2).toHaveBeenNthCalledWith(1, 7); @@ -21,9 +20,4 @@ test('should work basic operations with selector', () => { a.val++; expect(spy_1).toHaveBeenNthCalledWith(2, 8); expect(spy_2).toHaveBeenNthCalledWith(2, 8); - - free(); - a.val++; - expect(spy_1).toBeCalledTimes(2); - expect(spy_2).toBeCalledTimes(2); }); diff --git a/tests/signal.test.ts b/tests/builder-5-signal.test.ts similarity index 70% rename from tests/signal.test.ts rename to tests/builder-5-signal.test.ts index 29b5a3bd..5116f9a2 100644 --- a/tests/signal.test.ts +++ b/tests/builder-5-signal.test.ts @@ -1,15 +1,14 @@ -import { signal, cycle, on, value } from '../src'; +import { _signal, cycle, _on, _value } from '../src'; test('should work signal different using', () => { const spy = jest.fn(); - const a = signal(10); - on(a, spy); + const a = _signal(10); + _on(a, spy); - const [get] = a; + const {get} = a; expect(a.val).toBe(10); - expect(a[0]()).toBe(10); expect(get()).toBe(10); expect(spy).toBeCalledTimes(0); @@ -19,7 +18,6 @@ test('should work signal different using', () => { a(a.val + 5); expect(a.val).toBe(15); - expect(a[0]()).toBe(15); expect(get()).toBe(15); expect(a.get()).toBe(15); @@ -30,9 +28,9 @@ test('should work signal different using', () => { test('should work signal in cycle', () => { const spy = jest.fn(); - const a = signal(); + const a = _signal(); cycle(() => { - const data = a[0](); + const data = a.get(); spy(data); }); @@ -48,9 +46,9 @@ test('should work signal in cycle', () => { test('should work signal as promise', async () => { const spy = jest.fn(); - const a = signal(); + const a = _signal(); const fn = async () => { - spy(await a); + spy(await a.promise); }; fn(); expect(spy).toBeCalledTimes(0); @@ -69,8 +67,8 @@ test('should work signal as promise', async () => { test('should work signal in on', () => { const spy = jest.fn(); - const a = signal<'up' | 'down'>(); - on(a, v => { + const a = _signal<'up' | 'down'>(); + _on(a, v => { spy(v); }); @@ -86,8 +84,8 @@ test('should work signal in on', () => { test('should work signal with transform', () => { const spy = jest.fn(); - const a = signal(0).wrap((s: string) => parseInt(s) + 10); - on(a, v => { + const a = _signal(0).pre((s: string) => parseInt(s) + 10); + _on(a, v => { spy(v); }); @@ -101,31 +99,30 @@ test('should work signal with transform', () => { }); test('should work signal from', async () => { - const v = value(1); - const s = signal.from(v.select(v => v + v)); + const v = _value(1); + const s = _signal.from(v.select(v => v + v)); setTimeout(() => (v.val = 2), 100); expect(s.val).toBe(2); - expect(await s).toBe(4); + expect(await s.promise).toBe(4); }); test('should work signal combine', async () => { const spy = jest.fn(); - const v = value(1); - const s = signal.from(v.select(v => v + v)); + const v = _value(1); + const s = _signal.from(v.select(v => v + v), (k) => v.update(_v => _v + k)); - const c = signal.combine(v, s); - c.watch(v => spy(v)); + const c = _signal.combine([v, s]); + c.to(v => spy(v)); expect(c.val).toEqual([1, 2]); - s(10); - s(10); + s(4); + s(4); v(2); v(2); - expect(spy).toHaveBeenNthCalledWith(1, [1, 10]); - expect(spy).toHaveBeenNthCalledWith(2, [1, 10]); - expect(spy).toHaveBeenNthCalledWith(3, [2, 10]); // Todo: hmm - expect(spy).toHaveBeenNthCalledWith(4, [2, 4]); - expect(spy).toBeCalledTimes(4); + expect(spy).toHaveBeenNthCalledWith(1, [5, 10]); + expect(spy).toHaveBeenNthCalledWith(2, [9, 18]); + expect(spy).toHaveBeenNthCalledWith(3, [2, 4]); + expect(spy).toBeCalledTimes(3); }); diff --git a/tests/builder-5-trigger.test.ts b/tests/builder-5-trigger.test.ts new file mode 100644 index 00000000..595260c0 --- /dev/null +++ b/tests/builder-5-trigger.test.ts @@ -0,0 +1,144 @@ +import { _on, _signal, _sync, _value } from '../src'; +import { delay } from './lib'; + +test('should work signal.trigger with one value', async () => { + const spy = jest.fn(); + const a = _signal.trigger(0); + _on(a, spy); + + expect(a.val).toBe(0); + + const m = async () => { + expect(await a.promise).toBe(1); + }; + const k = async () => { + await delay(10); + a(1); + }; + await Promise.all([m(), k()]); + + a(2); + expect(a.val).toBe(1); + expect(await a.promise).toBe(1); + + expect(spy).toBeCalledTimes(1); + expect(spy).toBeCalledWith(1, 0); +}); + +test('should work signal.trigger with configured .pre', async () => { + // const spy = jest.fn(); + // const a = _signal.trigger(0).pre(() => 1); + // _on(a, spy); + + // expect(a.val).toBe(0); + // a(); + // expect(a.val).toBe(1); + // expect(await a.promise).toBe(1); + + // expect(spy).toBeCalledTimes(1); + // expect(spy).toBeCalledWith(1, 0); +}); + +test('should work signal.trigger reset', async () => { + const spy = jest.fn(); + const a = _signal.trigger(0).pre(() => 1); + _on(a, spy); + + expect(a.val).toBe(0); + a(); + expect(a.val).toBe(1); + a.reset(); + expect(a.val).toBe(0); + + const m = async () => { + expect(await a.promise).toBe(1); + }; + const k = async () => { + await delay(10); + a(); + }; + await Promise.all([m(), k()]); + + expect(a.val).toBe(1); + + expect(spy).toBeCalledTimes(3); + expect(spy).toHaveBeenNthCalledWith(1, 1, 0); + expect(spy).toHaveBeenNthCalledWith(2, 0, 1); + expect(spy).toHaveBeenNthCalledWith(3, 1, 0); +}); + +test('should work _signal.trigger wrapped', async () => { + const spy = jest.fn(); + const a = _signal.trigger(0) + .pre((v: number) => v * 2) + .pre(() => 10); + _on(a, spy); + + expect(a.val).toBe(0); + a(); + expect(a.val).toBe(20); + a.reset(); + expect(a.val).toBe(0); + + const m = async () => { + expect(await a.promise).toBe(20); + }; + const k = async () => { + await delay(10); + a(); + }; + await Promise.all([m(), k()]); + + expect(a.val).toBe(20); + + expect(spy).toBeCalledTimes(3); + expect(spy).toHaveBeenNthCalledWith(1, 20, 0); + expect(spy).toHaveBeenNthCalledWith(2, 0, 20); + expect(spy).toHaveBeenNthCalledWith(3, 20, 0); +}); + + +// test('should work ready from value', async () => { +// const spy = jest.fn(); + +// const s = value(1); +// const r = ready.from(s); + +// sync(r, spy); + +// s(1); +// s(2); +// s(3); + +// expect(spy).toBeCalledTimes(2); +// expect(spy).toHaveBeenNthCalledWith(1, 1); +// expect(spy).toHaveBeenNthCalledWith(2, 2, 1); +// }); + +// test('should work ready from signal', async () => { +// const spy = jest.fn(); + +// const s = _signal(1); +// const r = ready.from(s); + +// sync(r, spy); + +// s(1); +// s(3); + +// expect(spy).toBeCalledTimes(2); +// expect(spy).toHaveBeenNthCalledWith(1, 1); +// expect(spy).toHaveBeenNthCalledWith(2, 1, 1); +// }); + +// test('should work ready resolved', async () => { +// const r = ready.resolved(1); +// expect(r.val).toBe(1); +// expect(await r).toBe(1); +// }); + +// test('should work ready with undefined resolved', async () => { +// const r = ready.resolved(); +// expect(r.val).toBe(void 0); +// expect(await r).toBe(void 0); +// }); diff --git a/tests/_api.test.ts b/tests/builder-6.test.ts similarity index 99% rename from tests/_api.test.ts rename to tests/builder-6.test.ts index 6ef90355..2cb2d497 100644 --- a/tests/_api.test.ts +++ b/tests/builder-6.test.ts @@ -1365,10 +1365,3 @@ test('should work _signal with as.value', () => { expect(spy_s).toBeCalledWith(1, 1); spy_s.mockReset(); expect(spy_v).toBeCalledTimes(0); }); - - - - - - - diff --git a/tests/isolate.test.ts b/tests/isolate.test.ts index 8a31a65c..0c82a2a6 100644 --- a/tests/isolate.test.ts +++ b/tests/isolate.test.ts @@ -1,57 +1,57 @@ -import { isolate, effect, shared, free, un } from '../src'; +import { isolate, shared, free, un } from '../src'; test('should work basic operations with isolate', async () => { - const destr_1 = jest.fn(); - const destr_2 = jest.fn(); - const destr_3 = jest.fn(); - const destr_4 = jest.fn(); - let unsub: any; - let unsubs: any; - const A = () => { - effect(() => () => destr_1()); - const finish = isolate(); - unsub = effect(() => () => destr_2()); - effect(() => () => destr_3()); - unsubs = finish(); - un(() => destr_4()); - }; - - shared(A); - free(); - expect(destr_1).toBeCalled(); - expect(destr_4).toBeCalled(); - expect(destr_2).not.toBeCalled(); - - unsub(); - expect(destr_2).toBeCalled(); - - unsubs(); - expect(destr_3).toBeCalled(); + // const destr_1 = jest.fn(); + // const destr_2 = jest.fn(); + // const destr_3 = jest.fn(); + // const destr_4 = jest.fn(); + // let unsub: any; + // let unsubs: any; + // const A = () => { + // effect(() => () => destr_1()); + // const finish = isolate(); + // unsub = effect(() => () => destr_2()); + // effect(() => () => destr_3()); + // unsubs = finish(); + // un(() => destr_4()); + // }; + + // shared(A); + // free(); + // expect(destr_1).toBeCalled(); + // expect(destr_4).toBeCalled(); + // expect(destr_2).not.toBeCalled(); + + // unsub(); + // expect(destr_2).toBeCalled(); + + // unsubs(); + // expect(destr_3).toBeCalled(); }); test('should work isolate with argument', async () => { - const destr_1 = jest.fn(); - let unsub_1: any, unsub_2: any; - const A = () => { - effect(() => () => destr_1()); - unsub_1 = isolate(effect(() => () => destr_1())); - effect(() => () => destr_1()); - unsub_2 = isolate(effect(() => () => destr_1())); - }; - - shared(A); - free(); - expect(destr_1).toBeCalledTimes(2); - - unsub_1(); - expect(destr_1).toBeCalledTimes(3); - unsub_2(); - expect(destr_1).toBeCalledTimes(4); + // const destr_1 = jest.fn(); + // let unsub_1: any, unsub_2: any; + // const A = () => { + // effect(() => () => destr_1()); + // unsub_1 = isolate(effect(() => () => destr_1())); + // effect(() => () => destr_1()); + // unsub_2 = isolate(effect(() => () => destr_1())); + // }; + + // shared(A); + // free(); + // expect(destr_1).toBeCalledTimes(2); + + // unsub_1(); + // expect(destr_1).toBeCalledTimes(3); + // unsub_2(); + // expect(destr_1).toBeCalledTimes(4); }); test('should work isolate with no context', async () => { - const destr_1 = jest.fn(); - const unsub_1 = isolate(effect(() => () => destr_1())); - unsub_1(); - expect(destr_1).toBeCalledTimes(1); + // const destr_1 = jest.fn(); + // const unsub_1 = isolate(effect(() => () => destr_1())); + // unsub_1(); + // expect(destr_1).toBeCalledTimes(1); }); diff --git a/tests/ready.test.ts b/tests/ready.test.ts deleted file mode 100644 index 96f82ced..00000000 --- a/tests/ready.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { on, ready, signal, sync, value } from '../src'; -import { delay } from './lib'; - -test('should work ready with one value', async () => { - const spy = jest.fn(); - const a = ready(0); - on(a, spy); - - expect(a.val).toBe(0); - - const m = async () => { - expect(await a).toBe(1); - }; - const k = async () => { - await delay(10); - a(1); - }; - await Promise.all([m(), k()]); - - a(2); - expect(a.val).toBe(1); - expect(await a).toBe(1); - - expect(spy).toBeCalledTimes(1); - expect(spy).toBeCalledWith(1, 0); -}); - -test('should work ready with two values', async () => { - const spy = jest.fn(); - const a = signal.ready(0).to(1); - on(a, spy); - - expect(a.val).toBe(0); - a(); - expect(a.val).toBe(1); - expect(await a).toBe(1); - - expect(spy).toBeCalledTimes(1); - expect(spy).toBeCalledWith(1, 0); -}); - -test('should work ready reset', async () => { - const spy = jest.fn(); - const a = ready(0).to(1); - on(a, spy); - - expect(a.val).toBe(0); - a(); - expect(a.val).toBe(1); - a.reset(); - expect(a.val).toBe(0); - - const m = async () => { - expect(await a).toBe(1); - }; - const k = async () => { - await delay(10); - a(); - }; - await Promise.all([m(), k()]); - - expect(a.val).toBe(1); - - expect(spy).toBeCalledTimes(3); - expect(spy).toHaveBeenNthCalledWith(1, 1, 0); - expect(spy).toHaveBeenNthCalledWith(2, 0, 1); - expect(spy).toHaveBeenNthCalledWith(3, 1, 0); -}); - -test('should work ready wrapped to', async () => { - const spy = jest.fn(); - const a = ready(0) - .wrap((v: number) => v * 2) - .to(10); - on(a, spy); - - expect(a.val).toBe(0); - a(); - expect(a.val).toBe(20); - a.reset(); - expect(a.val).toBe(0); - - const m = async () => { - expect(await a).toBe(20); - }; - const k = async () => { - await delay(10); - a(); - }; - await Promise.all([m(), k()]); - - expect(a.val).toBe(20); - - expect(spy).toBeCalledTimes(3); - expect(spy).toHaveBeenNthCalledWith(1, 20, 0); - expect(spy).toHaveBeenNthCalledWith(2, 0, 20); - expect(spy).toHaveBeenNthCalledWith(3, 20, 0); -}); - -test('should work ready from value', async () => { - const spy = jest.fn(); - - const s = value(1); - const r = ready.from(s); - - sync(r, spy); - - s(1); - s(2); - s(3); - - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenNthCalledWith(1, 1); - expect(spy).toHaveBeenNthCalledWith(2, 2, 1); -}); - -test('should work ready from signal', async () => { - const spy = jest.fn(); - - const s = signal(1); - const r = ready.from(s); - - sync(r, spy); - - s(1); - s(3); - - expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenNthCalledWith(1, 1); - expect(spy).toHaveBeenNthCalledWith(2, 1, 1); -}); - -test('should work ready resolved', async () => { - const r = ready.resolved(1); - expect(r.val).toBe(1); - expect(await r).toBe(1); -}); - -test('should work ready with undefined resolved', async () => { - const r = ready.resolved(); - expect(r.val).toBe(void 0); - expect(await r).toBe(void 0); -}); diff --git a/tests/shared.test.ts b/tests/shared.test.ts index 509f9387..8dec1f66 100644 --- a/tests/shared.test.ts +++ b/tests/shared.test.ts @@ -1,4 +1,4 @@ -import { shared, initial, free, effect, mock, unmock } from '../src'; +import { shared, initial, free, un, mock, unmock } from '../src'; test('should work initial data with shared', () => { const spy = jest.fn(); @@ -7,7 +7,7 @@ test('should work initial data with shared', () => { class A { constructor(data: typeof a) { spy(data); - effect(() => destr); + un(destr); } } shared(A); diff --git a/tests/use-local.test.tsx b/tests/use-local.test.tsx index 17980a9d..26f58c06 100644 --- a/tests/use-local.test.tsx +++ b/tests/use-local.test.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { mount } from 'enzyme'; -import { useLocal, effect, prop } from '../src'; +import { useLocal, un, prop } from '../src'; test('should work useLocal function', () => { let constr = jest.fn(); @@ -10,7 +10,7 @@ test('should work useLocal function', () => { class Unit { constructor(...args: any[]) { constr(...args); - effect(() => spy); + un(spy); } } diff --git a/tests/use-scoped.test.tsx b/tests/use-scoped.test.tsx index f800bf44..b8439227 100644 --- a/tests/use-scoped.test.tsx +++ b/tests/use-scoped.test.tsx @@ -1,16 +1,14 @@ import React from 'react'; import { mount } from 'enzyme'; -import { useScoped, Scope, effect } from '../src'; +import { useScoped, Scope, un } from '../src'; test('should work useScoped function', async () => { let up = jest.fn(); let down = jest.fn(); const unit = () => { - effect(() => { - up(); - return () => down(); - }); + up(); + un(down); }; function A() { From 92d9329a268d3066930993651f0e6eac0fd49c68 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 16:39:09 +0700 Subject: [PATCH 059/100] chore: todos updated --- src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6b97958a..e8f5cf09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,12 +102,16 @@ const def_prop = Object.defineProperty; /* TODOs: [] test case "should work signal.trigger with configured .pre" - [] test cases for "isolate" (remove effect) + [] test cases for "isolate" (after effect will be removed) + [] migrate all old test cases to new api [] signal.trigger.from [] value.trigger.from [] signal.trigger.resolved [] value.trigger.resolved + [] .as.trigger + [] .as.value.trigger + [] .as.signal.trigger [] to remove loop From 6ff907777efc758058b92d4815dab85bcc4ae2e3 Mon Sep 17 00:00:00 2001 From: betula Date: Sat, 22 May 2021 19:33:03 +0700 Subject: [PATCH 060/100] chore: todos updated --- src/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index e8f5cf09..681846ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,18 +101,21 @@ const def_prop = Object.defineProperty; /* TODOs: - [] test case "should work signal.trigger with configured .pre" - [] test cases for "isolate" (after effect will be removed) [] migrate all old test cases to new api - [] signal.trigger.from - [] value.trigger.from - [] signal.trigger.resolved - [] value.trigger.resolved [] .as.trigger [] .as.value.trigger [] .as.signal.trigger + [] signal.trigger.resolved + [] value.trigger.resolved + + [] test case "should work signal.trigger with configured .pre" + [] test cases for "isolate" (after effect will be removed) + + [] signal.trigger.from (optional - maybe no necessary) + [] value.trigger.from (optional - maybe no necessary) + [] to remove loop [] rename _value to value From 7780ab9bf198b6c820ed589e2cb304f33d990769 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 24 May 2021 10:19:19 +0700 Subject: [PATCH 061/100] feat: isolate and local.inject updated --- src/index.ts | 69 ++++++++++++++----- .../{hook.test.tsx => local-inject.test.tsx} | 10 +-- 2 files changed, 58 insertions(+), 21 deletions(-) rename tests/{hook.test.tsx => local-inject.test.tsx} (86%) diff --git a/src/index.ts b/src/index.ts index 681846ed..894de5cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,8 @@ export { _untrack, _on, _sync, + _isolate, + _local, value, selector, @@ -157,7 +159,7 @@ const un_flow = (a,b,c) => ((a = flow(a,b,c)), un(a[2]), a); // -// Entity builder for value, signal and etc. Typings. +// Typings. // type ValueFactory = { @@ -175,11 +177,24 @@ type ValueFactory = { type SelectorFactory = { (fn: () => any): any; } -type SignalFactory = ValueFactory; - +type SignalFactory = { + (initial?: any): any; + trigger: { + (initial?: any): any; + flag: { + (initial?: any): any; + invert: { (initial?: any): any } + } + }; + from: { (get: () => any, set?: (v) => any): any }, + combine: { (cfg: any): any } +}; +type Local = { + inject(fn: () => void): void; +} // -// Entity builder implementation. +// Entity builder implementation for value, signal and etc. // const pure_fn = function () {}; @@ -810,7 +825,14 @@ const _untrack = (fn) => { finally { finish() } }; +// +// Realar internal +// +const call_fns_array = (arr) => arr.forEach(fn => fn()); +const throw_hook_error = () => { + throw new Error('Hook section available only at useLocal'); +} // // Realar exportable api @@ -827,9 +849,24 @@ _on[key_once] = _on_once; const _sync = (target, fn) => reactionable_subscribe(target, fn, 0, 1); +const local_inject = (fn) => { + if (!context_hooks) throw_hook_error(); + fn && context_hooks.push(fn); +} +const _local = {} as Local; +_local.inject = local_inject; - - +const _isolate = (fn?: any) => { + let unsubs; + const stack = context_unsubs; + context_unsubs = []; + try { fn() } + finally { + unsubs = context_unsubs; + context_unsubs = stack; + } + return () => unsubs && call_fns_array(unsubs); +} @@ -1430,7 +1467,7 @@ function isolate(fn?: any) { const unsubs = context_unsubs; context_unsubs = stack; return () => { - if (unsubs) call_array(unsubs); + if (unsubs) call_fns_array(unsubs); }; }; } @@ -1494,18 +1531,18 @@ function inst( return [instance, unsub, hooks]; } -function throw_hook_error() { - throw new Error('Hook section available only at useLocal'); -} +// function throw_hook_error() { +// throw new Error('Hook section available only at useLocal'); +// } function hook(fn: () => void): void { if (!context_hooks) throw_hook_error(); fn && context_hooks.push(fn); } -function call_array(arr: (() => void)[]) { - arr.forEach(fn => fn()); -} +// function call_array(arr: (() => void)[]) { +// arr.forEach(fn => fn()); +// } function get_scope_context(): Context { return scope_context ? scope_context : (scope_context = (createContext as any)()); @@ -1534,7 +1571,7 @@ function observe(FunctionComponent: T): T { const Scope: FC = ({ children }) => { const h = useMemo(() => [new Map(), [], []], []) as any; - useEffect(() => () => call_array(h[1]), []); + useEffect(() => () => call_fns_array(h[1]), []); return createElement(get_scope_context().Provider, { value: h }, children); }; @@ -1562,7 +1599,7 @@ function useLocal( ): M { const h = useMemo(() => { const i = inst(target, deps, 1); - const call_hooks = () => call_array(i[2]); + const call_hooks = () => call_fns_array(i[2]); return [i[0], () => i[1], call_hooks] as any; }, deps); h[2](); @@ -1599,7 +1636,7 @@ function useValue(target: Reactionable, deps: any[] = []): T { function free() { try { - call_array(shared_unsubs); + call_fns_array(shared_unsubs); } finally { shareds.clear(); initial_data = const_undef; diff --git a/tests/hook.test.tsx b/tests/local-inject.test.tsx similarity index 86% rename from tests/hook.test.tsx rename to tests/local-inject.test.tsx index 9fa6b256..d0c6c2be 100644 --- a/tests/hook.test.tsx +++ b/tests/local-inject.test.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import { mount } from 'enzyme'; -import { useLocal, hook, useScoped, Scope } from '../src'; +import { useLocal, _local, useScoped, Scope } from '../src'; -test('should work hook in useLocal function', () => { +test('should work local.inject in useLocal function', () => { let spy = jest.fn(); const unit = (value: number) => { - hook(() => { + _local.inject(() => { const [a, set_a] = useState(0); if (value !== a) set_a(value); spy(a); @@ -36,7 +36,7 @@ test('should work hook in useLocal function', () => { test('should throw exception if not in context', () => { expect(() => { - hook(() => {}); + _local.inject(() => {}); }).toThrow('Hook section available only at useLocal'); }); @@ -45,7 +45,7 @@ test('should throw exception if not useLocal', () => { console.error = () => {}; expect(() => { const unit = () => { - hook(() => {}); + _local.inject(() => {}); }; function A() { useScoped(unit); From 86a0d769c10cc36349d9021cd54cfb0007ef14d5 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 24 May 2021 10:20:40 +0700 Subject: [PATCH 062/100] feat: loop removed --- src/index.ts | 19 ++++--------------- tests/loop.test.ts | 40 ---------------------------------------- 2 files changed, 4 insertions(+), 55 deletions(-) delete mode 100644 tests/loop.test.ts diff --git a/src/index.ts b/src/index.ts index 894de5cd..9a27ec3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,6 @@ export { hook, sync, cycle, - loop, pool, stoppable, isolate, @@ -105,6 +104,8 @@ const def_prop = Object.defineProperty; TODOs: [] migrate all old test cases to new api + [] remove old value builder + [] .as.trigger [] .as.value.trigger [] .as.signal.trigger @@ -118,8 +119,6 @@ const def_prop = Object.defineProperty; [] signal.trigger.from (optional - maybe no necessary) [] value.trigger.from (optional - maybe no necessary) - [] to remove loop - [] rename _value to value [] rename _signal to signal [] rename _selector to selector @@ -1327,18 +1326,6 @@ function flow_filter(target: Reactionable, fn: (data: T) => boolean) { return f; } -function loop(body: () => Promise) { - let running = 1; - const fn = async () => { - while (running) await body(); - }; - const unsub = () => { - if (running) running = 0; - }; - un(unsub); - fn(); - return unsub; -} function stoppable(): StopSignal { if (!stoppable_context) throw new Error('Parent context not found'); @@ -1634,6 +1621,8 @@ function useValue(target: Reactionable, deps: any[] = []): T { return h[2] ? h[0]() : h[0]; } + + function free() { try { call_fns_array(shared_unsubs); diff --git a/tests/loop.test.ts b/tests/loop.test.ts deleted file mode 100644 index 2adc2feb..00000000 --- a/tests/loop.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { loop, signal, on } from '../src'; -import { delay } from './lib'; - -test('should work basic operations with loop', async () => { - const spy = jest.fn(); - const a = signal(); - const b = signal(); - const c = signal(); - - const stop = loop(async () => { - await Promise.all([a, b]); - c(); - }); - on(c, () => spy()); - - await delay(); - expect(spy).not.toBeCalled(); - a(); - await delay(); - expect(spy).not.toBeCalled(); - b(); - await delay(); - expect(spy).toBeCalled(); - - a(); - b(); - await delay(); - expect(spy).toBeCalledTimes(2); - - stop(); - a(); - b(); - await delay(); - expect(spy).toBeCalledTimes(3); - - a(); - b(); - await delay(); - expect(spy).toBeCalledTimes(3); -}); From ca2d8cd422d1e81191a00d4f6fa14aa73505f46f Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 24 May 2021 11:30:30 +0700 Subject: [PATCH 063/100] feat: .once added to .update.by, .reset.by and .reinit.by --- src/index.ts | 95 +++++++++++-------- .../filter.test.ts} | 2 +- .../selector.test.ts} | 2 +- .../signal.test.ts} | 2 +- .../trigger.test.ts} | 4 +- tests/{ => builder-5}/value.test.ts | 17 ++-- tests/builder-6.test.ts | 2 +- 7 files changed, 71 insertions(+), 53 deletions(-) rename tests/{builder-5-filter.test.ts => builder-5/filter.test.ts} (94%) rename tests/{builder-5-selector.test.ts => builder-5/selector.test.ts} (90%) rename tests/{builder-5-signal.test.ts => builder-5/signal.test.ts} (98%) rename tests/{builder-5-trigger.test.ts => builder-5/trigger.test.ts} (97%) rename tests/{ => builder-5}/value.test.ts (74%) diff --git a/src/index.ts b/src/index.ts index 9a27ec3b..86ba60f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -407,47 +407,47 @@ const prop_factory_dirty_required_initial = (ctx) => { const trait_ent_update = (ctx, fn) => (fn && ctx[key_set](fn(_untrack(ctx[key_get])))); const trait_ent_update_untrack = make_trait_ent_pure_fn_untrack(trait_ent_update); -const trait_ent_update_by = (ctx, src, fn) => { - const src_get = src[key_get] || src; - const e = un_expr(src_get, fn - ? () => { - try { - ctx[key_set](fn(ctx[key_get](), src_get(), prev_value)); - } finally { prev_value = e[0](); } - } - : () => (ctx[key_set](src_get()), (prev_value = e[0]())) - ); - let prev_value = e[0](); - return ctx; -}; +const trait_ent_update_by = (ctx, src, fn) => ( + reactionable_subscribe(src, fn + ? (src_value, src_prev_value) => ctx[key_set](fn(ctx[key_get](), src_value, src_prev_value)) + : (src_value) => ctx[key_set](src_value) + ), + ctx +); +const trait_ent_update_by_once = (ctx, src, fn) => ( + reactionable_subscribe(src, fn + ? (src_value, src_prev_value) => ctx[key_set](fn(ctx[key_get](), src_value, src_prev_value)) + : (src_value) => ctx[key_set](src_value), + 1 + ), + ctx +); const trait_ent_sync = (ctx, fn) => (reactionable_subscribe(ctx, fn, 0, 1), ctx); const trait_ent_reset = (ctx) => { ctx[key_promise_internal] = 0; ctx[key_handler][1](ctx[key_handler][key_initial]); ctx[key_handler][key_touched_internal] = 0; }; -const trait_ent_reset_by = (ctx, src) => { - const src_get = src[key_get] || src; - const e = un_expr(src_get, () => { - trait_ent_reset(ctx); - e[0]() - }); - e[0](); - return ctx; -}; +const trait_ent_reset_by = (ctx, src) => ( + reactionable_subscribe(src, trait_ent_reset.bind(const_undef, ctx)), + ctx +); +const trait_ent_reset_by_once = (ctx, src) => ( + reactionable_subscribe(src, trait_ent_reset.bind(const_undef, ctx), 1), + ctx +); const trait_ent_reinit = (ctx, initial) => { ctx[key_handler][key_initial] = initial; trait_ent_reset(ctx); }; -const trait_ent_reinit_by = (ctx, src) => { - const src_get = src[key_get] || src; - const e = un_expr(src_get, () => { - trait_ent_reinit(ctx, src_get()); - e[0](); - }); - e[0](); - return ctx; -}; +const trait_ent_reinit_by = (ctx, src) => ( + reactionable_subscribe(src, (src_value) => trait_ent_reinit(ctx, src_value)), + ctx +); +const trait_ent_reinit_by_once = (ctx, src) => ( + reactionable_subscribe(src, (src_value) => trait_ent_reinit(ctx, src_value), 1), + ctx +); const trait_ent_to = (ctx, fn) => (reactionable_subscribe(ctx, fn), ctx); const trait_ent_to_once = (ctx, fn) => (reactionable_subscribe(ctx, fn, 1), ctx); const trait_ent_select = (ctx, fn) => ( @@ -644,12 +644,19 @@ obj_def_prop_with_ns( ); obj_def_prop_promise(proto_entity_readable); +// writtable.update.by:ns +// .update.by.once +const proto_entity_writtable_update_by_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_update_by_ns, key_once, trait_ent_update_by_once); + // writtable.update:ns (track|untrack) -// .update.by +// .update.by:writtable.update.by:ns const proto_entity_writtable_update_ns = obj_create( make_proto_for_trackable_ns(trait_ent_update, trait_ent_update_untrack) ); -obj_def_prop_trait_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by); +obj_def_prop_trait_ns_with_ns(proto_entity_writtable_update_ns, key_by, trait_ent_update_by, + proto_entity_writtable_update_by_ns +); // writtable.pre.filter:ns (track|untrack) // .pre.filter.not (track|untrack) @@ -692,15 +699,29 @@ obj_def_prop_trait_with_ns( proto_entity_writtable_pre_ns ); +// writtable_leaf.reset.by:ns +// .reset.by.once +const proto_entity_writtable_leaf_reset_by_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_leaf_reset_by_ns, key_once, trait_ent_reset_by_once); + // writtable_leaf.reset:ns -// .reset.by +// .reset.by:writtable_leaf.reset.by:ns const proto_entity_writtable_leaf_reset_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_leaf_reset_ns, key_by, trait_ent_reset_by); +obj_def_prop_trait_ns_with_ns(proto_entity_writtable_leaf_reset_ns, key_by, trait_ent_reset_by, + proto_entity_writtable_leaf_reset_by_ns +); + +// writtable_leaf.reinit.by:ns +// .reinit.by.once +const proto_entity_writtable_leaf_reinit_by_ns = obj_create(pure_fn); +obj_def_prop_trait_ns(proto_entity_writtable_leaf_reinit_by_ns, key_once, trait_ent_reinit_by_once); // writtable_leaf.reinit:ns -// .reinit.by +// .reinit.by:writtable_leaf.reinit.by:ns const proto_entity_writtable_leaf_reinit_ns = obj_create(pure_fn); -obj_def_prop_trait_ns(proto_entity_writtable_leaf_reinit_ns, key_by, trait_ent_reinit_by); +obj_def_prop_trait_ns_with_ns(proto_entity_writtable_leaf_reinit_ns, key_by, trait_ent_reinit_by, + proto_entity_writtable_leaf_reinit_by_ns +); // writtable_leaf <- writtable diff --git a/tests/builder-5-filter.test.ts b/tests/builder-5/filter.test.ts similarity index 94% rename from tests/builder-5-filter.test.ts rename to tests/builder-5/filter.test.ts index d0a0d07c..b66c417e 100644 --- a/tests/builder-5-filter.test.ts +++ b/tests/builder-5/filter.test.ts @@ -1,4 +1,4 @@ -import { _signal, _on } from '../src'; +import { _signal, _on } from '../../src'; test('should work basic operations with pre.filter method', async () => { const spy = jest.fn(); diff --git a/tests/builder-5-selector.test.ts b/tests/builder-5/selector.test.ts similarity index 90% rename from tests/builder-5-selector.test.ts rename to tests/builder-5/selector.test.ts index ee002868..7cb56bb3 100644 --- a/tests/builder-5-selector.test.ts +++ b/tests/builder-5/selector.test.ts @@ -1,4 +1,4 @@ -import { _sync, _value, _selector } from '../src'; +import { _sync, _value, _selector } from '../../src'; test('should work basic operations with selector', () => { const spy_1 = jest.fn(); diff --git a/tests/builder-5-signal.test.ts b/tests/builder-5/signal.test.ts similarity index 98% rename from tests/builder-5-signal.test.ts rename to tests/builder-5/signal.test.ts index 5116f9a2..32178987 100644 --- a/tests/builder-5-signal.test.ts +++ b/tests/builder-5/signal.test.ts @@ -1,4 +1,4 @@ -import { _signal, cycle, _on, _value } from '../src'; +import { _signal, cycle, _on, _value } from '../../src'; test('should work signal different using', () => { const spy = jest.fn(); diff --git a/tests/builder-5-trigger.test.ts b/tests/builder-5/trigger.test.ts similarity index 97% rename from tests/builder-5-trigger.test.ts rename to tests/builder-5/trigger.test.ts index 595260c0..4028b122 100644 --- a/tests/builder-5-trigger.test.ts +++ b/tests/builder-5/trigger.test.ts @@ -1,5 +1,5 @@ -import { _on, _signal, _sync, _value } from '../src'; -import { delay } from './lib'; +import { _on, _signal, _sync, _value } from '../../src'; +import { delay } from '../lib'; test('should work signal.trigger with one value', async () => { const spy = jest.fn(); diff --git a/tests/value.test.ts b/tests/builder-5/value.test.ts similarity index 74% rename from tests/value.test.ts rename to tests/builder-5/value.test.ts index c39e5e5c..aec35c69 100644 --- a/tests/value.test.ts +++ b/tests/builder-5/value.test.ts @@ -1,12 +1,12 @@ -import { sync, value, signal } from '../src'; +import { _sync as sync, _value as value, _signal as signal } from '../../src'; test('should work basic operations with value', () => { const spy = jest.fn(); const a = value(10); - const [get, set] = a; + const {get, set} = a; sync(() => a.val, spy); - expect(spy).toHaveBeenNthCalledWith(1, 10); + expect(spy).toHaveBeenNthCalledWith(1, 10, void 0); set(get() + 5); expect(spy).toHaveBeenNthCalledWith(2, 15, 10); @@ -14,7 +14,7 @@ test('should work basic operations with value', () => { a.val += 10; expect(spy).toHaveBeenNthCalledWith(3, 25, 15); - a[1](a[0]() + a[0]()); + a.set(a.get() + a.get()); expect(spy).toHaveBeenNthCalledWith(4, 50, 25); a(1); @@ -22,9 +22,6 @@ test('should work basic operations with value', () => { a(1); expect(spy).toHaveBeenCalledTimes(5); - - a.set(a.get() + 7); - expect(spy).toHaveBeenNthCalledWith(6, 8, 1); }); test('should work value update', () => { @@ -37,8 +34,8 @@ test('should work value sub method', () => { const a = value(0); const b = value(0); const r = signal(0); - a.sub(r, (a, r, r_prev) => a * 100 + r * 10 + r_prev); - a.sub( + a.update.by(r, (a, r, r_prev) => a * 100 + r * 10 + r_prev); + a.update.by( () => b.val + 1, (a, r, r_prev) => a * 100 + r * 10 + r_prev ); @@ -54,7 +51,7 @@ test('should work value sub method', () => { test('should work value sub once method', () => { const a = value(1); const r = signal(0); - a.sub.once(r, (a, r, r_prev) => a * 100 + r * 10 + r_prev); + a.update.by.once(r, (a, r, r_prev) => a * 100 + r * 10 + r_prev); r(5); r(6); diff --git a/tests/builder-6.test.ts b/tests/builder-6.test.ts index 2cb2d497..6ca2e416 100644 --- a/tests/builder-6.test.ts +++ b/tests/builder-6.test.ts @@ -1130,7 +1130,7 @@ test('should work _signal.from with two arguments', () => { const v = _signal.from(() => a.val + 1, (v) => a(v + v)); expect(v.val).toBe(1); (t = v.to), (t = t(spy)); - (t = t.update), (t = t.by), t(() => u.val); + (t = t.update), (t = t.by), t(u); a.val = 0; expect(v.val).toBe(1); From 142904f2d80f585179b3e66f42ce02901ae14253 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 24 May 2021 12:14:09 +0700 Subject: [PATCH 064/100] feat: contextual added --- src/index.ts | 60 ++++++++++++++++++++--------- tests/builder-5/filter.test.ts | 14 +++---- tests/{ => builder-5}/reset.test.ts | 2 +- tests/builder-5/selector.test.ts | 8 ++-- tests/builder-5/signal.test.ts | 28 +++++++------- tests/builder-5/trigger.test.ts | 14 +++---- 6 files changed, 75 insertions(+), 51 deletions(-) rename tests/{ => builder-5}/reset.test.ts (81%) diff --git a/src/index.ts b/src/index.ts index 86ba60f8..88b95365 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,8 @@ export { _sync, _isolate, _local, + _cycle, + _contextual, value, selector, @@ -191,6 +193,9 @@ type SignalFactory = { type Local = { inject(fn: () => void): void; } +type Contextual = { + stop: () => void; +} // // Entity builder implementation for value, signal and etc. @@ -240,6 +245,8 @@ const key_join = "join"; const key_value = "value"; const key_as = "as"; const key_op = "op"; +const key_inject = "inject"; +const key_stop = "stop"; const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -858,24 +865,6 @@ const throw_hook_error = () => { // Realar exportable api // -const un = (unsub: () => void) => { - unsub && context_unsubs && context_unsubs.push(unsub) -} - -const _on_once = (target, fn) => reactionable_subscribe(target, fn, 1); -const _on = (target, fn) => reactionable_subscribe(target, fn); - -_on[key_once] = _on_once; - -const _sync = (target, fn) => reactionable_subscribe(target, fn, 0, 1); - -const local_inject = (fn) => { - if (!context_hooks) throw_hook_error(); - fn && context_hooks.push(fn); -} -const _local = {} as Local; -_local.inject = local_inject; - const _isolate = (fn?: any) => { let unsubs; const stack = context_unsubs; @@ -888,7 +877,42 @@ const _isolate = (fn?: any) => { return () => unsubs && call_fns_array(unsubs); } +const un = (unsub: () => void) => { + unsub && context_unsubs && context_unsubs.push(unsub) +} + + +const local_inject = (fn) => { + if (!context_hooks) throw_hook_error(); + fn && context_hooks.push(fn); +} +const _local = {} as Local; +_local[key_inject] = local_inject; + + +const _on_once = (target, fn) => reactionable_subscribe(target, fn, 1); +const _on = (target, fn) => reactionable_subscribe(target, fn); + +_on[key_once] = _on_once; + +const _sync = (target, fn) => reactionable_subscribe(target, fn, 0, 1); + +const _cycle = (body) => { + const iter = () => { + const stack = stoppable_context; + stoppable_context = e[1]; + try { + e[0](); + } finally { + stoppable_context = stack; + } + }; + const e = un_expr(body, iter); + iter(); +} +const _contextual = {} as Contextual; +obj_def_prop(_contextual, key_stop, { get: () => stoppable_context }); diff --git a/tests/builder-5/filter.test.ts b/tests/builder-5/filter.test.ts index b66c417e..713981bf 100644 --- a/tests/builder-5/filter.test.ts +++ b/tests/builder-5/filter.test.ts @@ -1,14 +1,14 @@ -import { _signal, _on } from '../../src'; +import { _signal as signal, _on as on } from '../../src'; test('should work basic operations with pre.filter method', async () => { const spy = jest.fn(); - const a = _signal(0); - const b = _signal(0); + const a = signal(0); + const b = signal(0); const c = a.pre.filter(() => b.val); - _on(c, spy); + on(c, spy); c(1); b(1); @@ -25,12 +25,12 @@ test('should work basic operations with pre.filter method', async () => { test('should work basic operations with filter method', async () => { const spy = jest.fn(); - const a = _signal(0); - const b = _signal(0); + const a = signal(0); + const b = signal(0); const c = a.filter(() => b.val); - _on(c, spy); + on(c, spy); a(1); b(1); diff --git a/tests/reset.test.ts b/tests/builder-5/reset.test.ts similarity index 81% rename from tests/reset.test.ts rename to tests/builder-5/reset.test.ts index 5fd11470..afee2710 100644 --- a/tests/reset.test.ts +++ b/tests/builder-5/reset.test.ts @@ -1,4 +1,4 @@ -import { signal, value } from '../src'; +import { _signal as signal, _value as value } from '../../src'; test('should work basic operations with reset for differents', async () => { const s = signal(0); diff --git a/tests/builder-5/selector.test.ts b/tests/builder-5/selector.test.ts index 7cb56bb3..132fdcb7 100644 --- a/tests/builder-5/selector.test.ts +++ b/tests/builder-5/selector.test.ts @@ -1,14 +1,14 @@ -import { _sync, _value, _selector } from '../../src'; +import { _sync as sync, _value as value, _selector as selector } from '../../src'; test('should work basic operations with selector', () => { const spy_1 = jest.fn(); const spy_2 = jest.fn(); - const a = _value(7); - const s = _selector(() => (spy_1(a.val), a.val)); + const a = value(7); + const s = selector(() => (spy_1(a.val), a.val)); const {get} = s; - _sync(s, v => spy_2(v)); + sync(s, v => spy_2(v)); expect(spy_1).toHaveBeenNthCalledWith(1, 7); expect(spy_2).toHaveBeenNthCalledWith(1, 7); diff --git a/tests/builder-5/signal.test.ts b/tests/builder-5/signal.test.ts index 32178987..7520eace 100644 --- a/tests/builder-5/signal.test.ts +++ b/tests/builder-5/signal.test.ts @@ -1,10 +1,10 @@ -import { _signal, cycle, _on, _value } from '../../src'; +import { _signal as signal, _cycle as cycle, _on as on, _value as value } from '../../src'; test('should work signal different using', () => { const spy = jest.fn(); - const a = _signal(10); - _on(a, spy); + const a = signal(10); + on(a, spy); const {get} = a; @@ -28,7 +28,7 @@ test('should work signal different using', () => { test('should work signal in cycle', () => { const spy = jest.fn(); - const a = _signal(); + const a = signal(); cycle(() => { const data = a.get(); spy(data); @@ -46,7 +46,7 @@ test('should work signal in cycle', () => { test('should work signal as promise', async () => { const spy = jest.fn(); - const a = _signal(); + const a = signal(); const fn = async () => { spy(await a.promise); }; @@ -67,8 +67,8 @@ test('should work signal as promise', async () => { test('should work signal in on', () => { const spy = jest.fn(); - const a = _signal<'up' | 'down'>(); - _on(a, v => { + const a = signal<'up' | 'down'>(); + on(a, v => { spy(v); }); @@ -84,8 +84,8 @@ test('should work signal in on', () => { test('should work signal with transform', () => { const spy = jest.fn(); - const a = _signal(0).pre((s: string) => parseInt(s) + 10); - _on(a, v => { + const a = signal(0).pre((s: string) => parseInt(s) + 10); + on(a, v => { spy(v); }); @@ -99,8 +99,8 @@ test('should work signal with transform', () => { }); test('should work signal from', async () => { - const v = _value(1); - const s = _signal.from(v.select(v => v + v)); + const v = value(1); + const s = signal.from(v.select(v => v + v)); setTimeout(() => (v.val = 2), 100); expect(s.val).toBe(2); @@ -109,10 +109,10 @@ test('should work signal from', async () => { test('should work signal combine', async () => { const spy = jest.fn(); - const v = _value(1); - const s = _signal.from(v.select(v => v + v), (k) => v.update(_v => _v + k)); + const v = value(1); + const s = signal.from(v.select(v => v + v), (k) => v.update(_v => _v + k)); - const c = _signal.combine([v, s]); + const c = signal.combine([v, s]); c.to(v => spy(v)); expect(c.val).toEqual([1, 2]); diff --git a/tests/builder-5/trigger.test.ts b/tests/builder-5/trigger.test.ts index 4028b122..87936520 100644 --- a/tests/builder-5/trigger.test.ts +++ b/tests/builder-5/trigger.test.ts @@ -1,10 +1,10 @@ -import { _on, _signal, _sync, _value } from '../../src'; +import { _on as on, _signal as signal } from '../../src'; import { delay } from '../lib'; test('should work signal.trigger with one value', async () => { const spy = jest.fn(); - const a = _signal.trigger(0); - _on(a, spy); + const a = signal.trigger(0); + on(a, spy); expect(a.val).toBe(0); @@ -41,8 +41,8 @@ test('should work signal.trigger with configured .pre', async () => { test('should work signal.trigger reset', async () => { const spy = jest.fn(); - const a = _signal.trigger(0).pre(() => 1); - _on(a, spy); + const a = signal.trigger(0).pre(() => 1); + on(a, spy); expect(a.val).toBe(0); a(); @@ -69,10 +69,10 @@ test('should work signal.trigger reset', async () => { test('should work _signal.trigger wrapped', async () => { const spy = jest.fn(); - const a = _signal.trigger(0) + const a = signal.trigger(0) .pre((v: number) => v * 2) .pre(() => 10); - _on(a, spy); + on(a, spy); expect(a.val).toBe(0); a(); From 720ee4d85de7803638ad32524183c339b7f8ddf1 Mon Sep 17 00:00:00 2001 From: betula Date: Mon, 24 May 2021 14:34:11 +0700 Subject: [PATCH 065/100] feat: only the .6 code on working --- src/index.ts | 1794 ++++++++++------- tests/{wrap.test.ts => builder-5/pre.test.ts} | 24 +- tests/{watch.test.ts => builder-5/to.test.ts} | 15 +- tests/builder-5/trigger.test.ts | 4 +- tests/{ => builder-5}/view.test.ts | 4 +- tests/builder-6.test.ts | 2 +- ...ppable.test.ts => contextual-stop.test.ts} | 19 +- tests/isolate.test.ts | 2 +- tests/on.test.ts | 10 +- tests/pool.test.ts | 17 +- tests/prop.test.ts | 2 +- tests/sync.test.ts | 12 +- tests/use-value.test.tsx | 6 +- 13 files changed, 1068 insertions(+), 843 deletions(-) rename tests/{wrap.test.ts => builder-5/pre.test.ts} (53%) rename tests/{watch.test.ts => builder-5/to.test.ts} (68%) rename tests/{ => builder-5}/view.test.ts (85%) rename tests/{stoppable.test.ts => contextual-stop.test.ts} (75%) diff --git a/src/index.ts b/src/index.ts index 88b95365..b1399f28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,9 @@ import React, { Context, FC } from 'react'; -import rb, { transaction, untrack } from 'reactive-box'; +import rb from 'reactive-box'; + +// +// Exports +// export { _value, @@ -14,20 +18,15 @@ export { _cycle, _contextual, - value, - selector, prop, cache, - signal, - ready, - on, + // on, un, - hook, - sync, - cycle, + // sync, + // cycle, pool, - stoppable, - isolate, + // stoppable, + // isolate, shared, initial, observe, @@ -39,16 +38,74 @@ export { free, mock, unmock, - transaction, - untrack, - Ensurable, - Selector, - Value, - Signal, - StopSignal, - ReadySignal, }; + + +// +// Typings. +// + +type ValueFactory = { + (initial?: any): any; + trigger: { + (initial?: any): any; + flag: { + (initial?: any): any; + invert: { (initial?: any): any } + } + }; + from: { (get: () => any, set?: (v) => any): any }, + combine: { (cfg: any): any } +} +type SelectorFactory = { + (fn: () => any): any; +} +type SignalFactory = { + (initial?: any): any; + trigger: { + (initial?: any): any; + flag: { + (initial?: any): any; + invert: { (initial?: any): any } + } + }; + from: { (get: () => any, set?: (v) => any): any }, + combine: { (cfg: any): any } +}; + +type Local = { + inject(fn: () => void): void; +} +type Contextual = { + stop: () => void; +} +type Isolate = { + (fn): () => void; + unsafe: () => () => () => void; +} +type Transaction = { + (fn: () => T): T; + unsafe: () => () => void; +} +type Untrack = { + (fn: () => T): T; + unsafe: () => () => void; +} + + +type Reactionable = { get: () => T } | (() => T); + +type Pool = K & { + count: any; + threads: any; + pending: any; +}; + +// +// Global definitions +// + let react; let useRef: typeof React.useRef; @@ -76,20 +133,20 @@ try { }) as any; } -const key = 'val'; +// const key = 'val'; const shareds = new Map(); let initial_data: any; let context_unsubs: any; let context_hooks: any; let shared_unsubs = [] as any; -let is_sync: any; -let is_stop_signal: any; +// let is_sync: any; +// let is_stop_signal: any; let is_observe: any; let scope_context: any; let stoppable_context: any; -const def_prop = Object.defineProperty; +// const def_prop = Object.defineProperty; @@ -159,44 +216,6 @@ const un_expr = (a,b) => ((a = expr(a,b)), un(a[1]), a); const un_flow = (a,b,c) => ((a = flow(a,b,c)), un(a[2]), a); -// -// Typings. -// - -type ValueFactory = { - (initial?: any): any; - trigger: { - (initial?: any): any; - flag: { - (initial?: any): any; - invert: { (initial?: any): any } - } - }; - from: { (get: () => any, set?: (v) => any): any }, - combine: { (cfg: any): any } -} -type SelectorFactory = { - (fn: () => any): any; -} -type SignalFactory = { - (initial?: any): any; - trigger: { - (initial?: any): any; - flag: { - (initial?: any): any; - invert: { (initial?: any): any } - } - }; - from: { (get: () => any, set?: (v) => any): any }, - combine: { (cfg: any): any } -}; -type Local = { - inject(fn: () => void): void; -} -type Contextual = { - stop: () => void; -} - // // Entity builder implementation for value, signal and etc. // @@ -206,47 +225,48 @@ const pure_arrow_fn_returns_arg = (v) => v; const pure_arrow_fn_returns_not_arg = (v) => !v; const pure_arrow_fn_returns_undef = (() => {}) as any; -const key_proto = "__proto__"; -const key_get = "get"; -const key_set = "set"; -const key_promise = "promise"; +const key_proto = '__proto__'; +const key_get = 'get'; +const key_set = 'set'; +const key_promise = 'promise'; const key_promise_internal = new_symbol(); -const key_reset = "reset"; +const key_reset = 'reset'; const key_initial = new_symbol(); const key_dirty_handler = new_symbol(); -const key_dirty = "dirty"; -const key_sync = "sync"; +const key_dirty = 'dirty'; +const key_sync = 'sync'; const key_ctx = new_symbol(); -const key_by = "by"; -const key_reinit = "reinit"; -const key_update = "update"; -const key_val = "val"; -const key_once = "once"; -const key_to = "to"; -const key_select = "select"; -const key_view = "view"; +const key_by = 'by'; +const key_reinit = 'reinit'; +const key_update = 'update'; +const key_val = 'val'; +const key_once = 'once'; +const key_to = 'to'; +const key_select = 'select'; +const key_view = 'view'; const key_handler = new_symbol(); -const key_pre = "pre"; -const key_filter = "filter"; -const key_not = "not"; -const key_flow = "flow"; +const key_pre = 'pre'; +const key_filter = 'filter'; +const key_not = 'not'; +const key_flow = 'flow'; const key_reset_promise_by_reset = new_symbol(); const key_touched_internal = new_symbol(); -const key_trigger = "trigger"; -const key_flag = "flag"; -const key_invert = "invert"; -const key_from = "from"; +const key_trigger = 'trigger'; +const key_flag = 'flag'; +const key_invert = 'invert'; +const key_from = 'from'; const key_is_signal = new_symbol(); -const key_track = "track"; -const key_untrack = "untrack"; -const key_multiple = "multiple"; -const key_combine = "combine"; -const key_join = "join"; -const key_value = "value"; -const key_as = "as"; -const key_op = "op"; -const key_inject = "inject"; -const key_stop = "stop"; +const key_track = 'track'; +const key_untrack = 'untrack'; +const key_multiple = 'multiple'; +const key_combine = 'combine'; +const key_join = 'join'; +const key_value = 'value'; +const key_as = 'as'; +const key_op = 'op'; +const key_inject = 'inject'; +const key_stop = 'stop'; +const key_unsafe = 'unsafe'; const obj_def_prop_value = (obj, key, value) => ( obj_def_prop(obj, key, { value }), value @@ -836,21 +856,6 @@ _signal[key_from] = _signal_from; _signal[key_combine] = _signal_combine; -// -// Reactive box functions with additional abstraction -// - -const _transaction = (fn) => { - const finish = internal_transaction(); - try { return fn() } - finally { finish() } -}; - -const _untrack = (fn) => { - const finish = internal_untrack(); - try { return fn() } - finally { finish() } -}; // // Realar internal @@ -860,22 +865,43 @@ const call_fns_array = (arr) => arr.forEach(fn => fn()); const throw_hook_error = () => { throw new Error('Hook section available only at useLocal'); } +const internal_isolate = () => { + const stack = context_unsubs; + context_unsubs = []; + return () => { + const unsubs = context_unsubs; + context_unsubs = stack; + return () => unsubs && call_fns_array(unsubs); + }; +} // // Realar exportable api // -const _isolate = (fn?: any) => { +const _transaction = ((fn) => { + const finish = internal_transaction(); + try { return fn() } + finally { finish() } +}) as Transaction; +_transaction[key_unsafe] = internal_transaction; + +const _untrack = ((fn) => { + const finish = internal_untrack(); + try { return fn() } + finally { finish() } +}) as Untrack; +_untrack[key_unsafe] = internal_untrack; + +const _isolate = ((fn?: any) => { let unsubs; - const stack = context_unsubs; - context_unsubs = []; + const finish = internal_isolate(); try { fn() } - finally { - unsubs = context_unsubs; - context_unsubs = stack; - } - return () => unsubs && call_fns_array(unsubs); -} + finally { unsubs = finish() } + return unsubs; +}) as Isolate; +_isolate[key_unsafe] = internal_isolate; + const un = (unsub: () => void) => { unsub && context_unsubs && context_unsubs.push(unsub) @@ -912,598 +938,820 @@ const _cycle = (body) => { } const _contextual = {} as Contextual; -obj_def_prop(_contextual, key_stop, { get: () => stoppable_context }); - - +obj_def_prop(_contextual, key_stop, { + get() { + if (!stoppable_context) throw new Error('Parent context not found'); + return stoppable_context; + } +}); +// +// Shared technique abstraction +// +const initial = (data: any): void => { + initial_data = data; +} +const inst = ( + target: (new (...args: K) => M) | ((...args: K) => M), + args: K, + hooks_available?: any +): [M, () => void, (() => void)[]] => { + let instance, unsub, hooks; + const collect = internal_isolate(); + const track = internal_untrack(); + const stack = context_hooks; + context_hooks = []; + try { + instance = + target.prototype === const_undef + ? (target as any)(...args) + : new (target as any)(...args); + if (!hooks_available && context_hooks.length > 0) throw_hook_error(); + } finally { + unsub = collect(); + track(); + hooks = context_hooks; + context_hooks = stack; + } + return [instance, unsub, hooks]; +} +const shared = (target: (new (init?: any) => M) | ((init?: any) => M)): M => { + let instance = shareds.get(target); + if (!instance) { + const h = inst(target, [initial_data]); + instance = h[0]; + shared_unsubs.push(h[1]); + shareds.set(target, instance); + } + return instance; +} +const free = () => { + try { + call_fns_array(shared_unsubs); + } finally { + shareds.clear(); + initial_data = const_undef; + } +} +const mock = (target: (new (init?: any) => M) | ((init?: any) => M), mocked: M): M => ( + shareds.set(target, mocked), + mocked +) +const unmock = ( + target: (new (init?: any) => any) | ((init?: any) => any), + ...targets: ((new (init?: any) => any) | ((init?: any) => any))[] +) => ( + targets.concat(target).forEach(target => shareds.delete(target)) +) +// +// Decorator functions "prop" and "cache" +// +const obj_def_box_prop = (o: any, p: string | number | symbol, init?: any): any => { + const b = box(init && init()); + obj_def_prop(o, p, { get: b[0], set: b[1] }); +} +function prop(_proto: any, key: any, descriptor?: any): any { + const initializer = descriptor?.initializer; + return { + get() { + obj_def_box_prop(this, key, initializer); + return this[key]; + }, + set(value: any) { + obj_def_box_prop(this, key, initializer); + this[key] = value; + }, + }; +} +function cache(_proto: any, key: any, descriptor: any): any { + return { + get() { + const [get] = sel(descriptor.get); + obj_def_prop(this, key, { get }); + return this[key]; + }, + }; +} +// +// React bindings +// +function get_scope_context(): Context { + return scope_context ? scope_context : (scope_context = (createContext as any)()); +} +function useForceUpdate() { + return useReducer(() => [], [])[1] as () => void; +} +function observe(FunctionComponent: T): T { + return function (this: any) { + const forceUpdate = useForceUpdate(); + const ref = useRef<[T, () => void]>(); + if (!ref.current) ref.current = expr(FunctionComponent, forceUpdate); + useEffect(() => ref.current![1], []); + const stack = is_observe; + is_observe = 1; + try { + return (ref.current[0] as any).apply(this, arguments); + } finally { + is_observe = stack; + } + } as any; +} +const Scope: FC = ({ children }) => { + const h = useMemo(() => [new Map(), [], []], []) as any; + useEffect(() => () => call_fns_array(h[1]), []); + return createElement(get_scope_context().Provider, { value: h }, children); +}; +function useScoped(target: (new (init?: any) => M) | ((init?: any) => M)): M { + const context_data = useContext(get_scope_context()); + if (!context_data) { + throw new Error('"Scope" parent component didn\'t find'); + } + let instance; + if (context_data[0].has(target)) { + instance = context_data[0].get(target); + } else { + const h = inst(target, [initial_data]); + context_data[0].set(target, (instance = h[0])); + context_data[1].push(h[1]); + } + return instance; +} +function useLocal( + target: (new (...args: T) => M) | ((...args: T) => M), + deps = ([] as unknown) as T +): M { + const h = useMemo(() => { + const i = inst(target, deps, 1); + const call_hooks = () => call_fns_array(i[2]); + return [i[0], () => i[1], call_hooks] as any; + }, deps); + h[2](); + useEffect(h[1], [h]); + return h[0]; +} +function useValue(target: Reactionable, deps: any[] = []): T { + const forceUpdate = is_observe || useForceUpdate(); + const h = useMemo(() => { + if (!target) return [target, () => {}]; + if ((target as any)[key_get]) target = (target as any)[key_get]; + if (typeof target === 'function') { + if (is_observe) { + return [target, 0, 1]; + } else { + const [run, stop] = expr(target as any, () => { + forceUpdate(); + run(); + }); + run(); + return [target, () => stop, 1]; + } + } else { + return [target as any, () => {}]; + } + }, deps); + is_observe || useEffect(h[1], [h]); + return h[2] ? h[0]() : h[0]; +} +// +// Pool abstraction +// +function pool Promise>(body: K): Pool { + const threads = _value([]); + const count = threads.select(t => t.length); + const pending = count.select(c => c > 0); + function run(this: any) { + const stop = () => threads.update(t => t.filter(ctx => ctx !== stop)); + threads.update(t => t.concat(stop as any)); + const stack = stoppable_context; + stoppable_context = stop; + let ret; + try { + ret = (body as any).apply(this, arguments); + } finally { + stoppable_context = stack; + if (ret && ret.finally) { + ret.finally(stop); + } else { + stop(); + } + } + return ret; + } + run.count = count; + run.threads = threads; + run.pending = pending; + return run as any; +} -type Ensurable = T | void; -type Callable = { - name: never; - (data: T): void; -} & (T extends void - ? { - (): void; - } - : {}); - -type Reactionable = { 0: () => T } | [() => T] | (() => T); - -type Selector = { - 0: () => T; - readonly val: T; - get(): T; - free(): void; -} & [() => T] & { - view

(get: (data: T) => P): Selector

; - select

(get: (data: T) => P): Selector

; - select(): Selector; - - watch: { - (listener: (value: T, prev: T) => void): () => void; - once(listener: (value: T, prev: T) => void): () => void; - }; - - flow: { - filter(fn: (data: T) => any): Value>; - }; - }; -type Value = Callable & { - 0: () => K; - 1: (value: T) => void; - val: T & K; - get(): K; - update: (fn: (state: K) => T) => void; - sub: { - (reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; - once(reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; - }; - set(value: T): void; -} & { - wrap: { -

(set: () => T, get: (data: K) => P): Value; - (set: (data: M) => T, get: (data: K) => P): Value; - (set: () => T): Value; - (set: (data: M) => T): Value; - - filter(fn: (data: T) => any): Value; - }; - view

(get: (data: K) => P): Value; - select

(get: (data: K) => P): Selector

; - select(): Selector; - watch: { - (listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; - once(listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; - }; - reset(): void; - flow: { - filter(fn: (data: K) => any): Value>; - }; -} & { - [P in Exclude, number>]: never; - } & - [() => K, (value: T) => void]; - -type Signal< - T, - K = T, - X = {}, - E = { - reset(): void; - update: (fn: (state: K) => T) => void; - sub: { - (reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; - once(reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; - }; - set(value: T): void; - } -> = Callable & - Pick, 'then' | 'catch' | 'finally'> & { - 0: () => K; - 1: (value: T) => void; - readonly val: K; - get(): K; - } & { - wrap: { -

(set: () => T, get: (data: K) => P): Signal; - (set: (data: M) => T, get: (data: K) => P): Signal; - (set: () => T): Signal; - (set: (data: M) => T): Signal; - - filter(fn: (data: T) => any): Signal; - }; - - view

(get: (data: K) => P): Signal; - select

(get: (data: K) => P): Selector

; - select(): Selector; - - watch: { - (listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; - once(listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; - }; - - flow: { - filter(fn: (data: K) => any): Signal>; - }; - } & E & - X & - { - [P in Exclude, number>]: never; - } & - [() => K, (value: T) => void]; - -type StopSignal = Signal< - void, - boolean, - { - stop(): void; - set(): void; - sub: { - (reactionable: Reactionable): () => void; - once(reactionable: Reactionable): () => void; - }; - }, - {} ->; -type ReadySignal = Signal< - T, - K, - { - to(value: T): Signal; - } ->; -type Pool = K & { - count: Selector; - threads: Selector; - pending: Selector; -}; -function value(): Value; -function value(init: T): Value; -function value(init?: any): any { - const [get, set] = box(init) as any; - def_format(set, get, set); - set.reset = () => set(init); - return set; -} -function selector(body: () => T): Selector { - const [get, free] = sel(body); - const h = [] as any; - def_format(h, get); - h.free = free; - return h; -} -function signal(): Signal>; -function signal(init: T): Signal; -function signal(init?: any) { - let resolve: any; - const [get, set] = box([init]); - - const fn = function (data: any) { - const ready = resolve; - resolve = def_promisify(fn); - set([data]); - ready(data); - }; - resolve = def_promisify(fn); - def_format(fn, () => get()[0], fn, 0, 1); - fn.reset = () => set([init]); - return fn as any; -} -signal.stop = stop_signal; -signal.ready = ready; -signal.from = signal_from; -signal.combine = signal_combine; - -function ready(): ReadySignal>; -function ready(init: T): ReadySignal; -function ready(init?: any) { - let resolved = 0; - let resolve: any; - const [get, set] = box([init]); - - const fn = function (data: any) { - if (!resolved) { - resolved = 1; - set([data]); - resolve(data); - } - }; - resolve = def_promisify(fn); - def_format(fn, () => get()[0], fn, is_stop_signal, 1, 1); - if (!is_stop_signal) { - fn.reset = () => { - resolve = def_promisify(fn); - resolved = 0; - set([init]); - }; - } - return fn as any; -} -ready.flag = () => ready(false).to(true); -ready.from = ready_from; -ready.resolved = ready_resolved; -function ready_from(source: Reactionable): ReadySignal { - const fn = (source as any)[0] || (source as any); - const dest = ready(fn()); - on(source, dest); - return dest as any; -} -function ready_resolved(): ReadySignal; -function ready_resolved(value: T): ReadySignal; -function ready_resolved(value?: any): any { - const r = ready(value); - r(value); - return r; -} -function signal_from(source: Reactionable): Signal { - const fn = (source as any)[0] || (source as any); - const dest = signal(fn()); - on(source, dest); - return dest as any; -} -function signal_combine(): Signal<[]>; -function signal_combine(a: Reactionable): Signal<[A]>; -function signal_combine(a: Reactionable, b: Reactionable): Signal<[A, B]>; -function signal_combine( - a: Reactionable, - b: Reactionable, - c: Reactionable -): Signal<[A, B, C]>; -function signal_combine( - a: Reactionable, - b: Reactionable, - c: Reactionable, - d: Reactionable -): Signal<[A, B, C, D]>; -function signal_combine( - a: Reactionable, - b: Reactionable, - c: Reactionable, - d: Reactionable, - e: Reactionable -): Signal<[A, B, C, D, E]>; -function signal_combine(...sources: any): any { - const get = () => sources.map((src: any) => (src[0] || src)()); - const dest = signal(get()); - on([get], dest); - return dest as any; -} -function stop_signal(): StopSignal { - is_stop_signal = 1; - try { - const ctx = ready.flag() as any; - return (ctx.stop = ctx); - } finally { - is_stop_signal = 0; - } -} -function def_format( - ctx: any, - get: any, - set?: any, - no_update?: any, - readonly_val?: any, - has_to?: any -) { - if (!Array.isArray(ctx)) { - ctx[Symbol.iterator] = function* () { - yield get; - if (set) yield set; - }; - } - ctx[0] = get; - ctx.get = get; - const val_prop = { get } as any; - if (set) { - ctx[1] = set; - if (!no_update) { - ctx.update = (fn: any) => set(fn(get())); - } - ctx.set = set; - if (!readonly_val) val_prop.set = set; +// type Ensurable = T | void; - ctx.sub = (s: any, fn: any) => on(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : const_undef)); - ctx.sub.once = (s: any, fn: any) => - once(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : const_undef)); - } - def_prop(ctx, key, val_prop); +// type Callable = { +// name: never; +// (data: T): void; +// } & (T extends void +// ? { +// (): void; +// } +// : {}); - if (has_to) { - ctx.to = (value: any) => (wrap as any)(ctx, () => value); - } - if (set) { - ctx.wrap = (set: any, get: any) => wrap(ctx, set, get); - ctx.wrap.filter = (fn: any) => wrap(ctx, (v: any) => (fn(v) ? v : stoppable().stop())); - } - ctx.view = (get: any) => wrap(ctx, 0, get); - ctx.watch = (fn: any) => on(ctx, fn); - ctx.watch.once = (fn: any) => once(ctx, fn); +// type Reactionable = { 0: () => T } | [() => T] | (() => T); - ctx.select = (fn: any) => selector(fn ? () => fn(get()) : get); +// type Selector = { +// 0: () => T; +// readonly val: T; +// get(): T; +// free(): void; +// } & [() => T] & { +// view

(get: (data: T) => P): Selector

; +// select

(get: (data: T) => P): Selector

; +// select(): Selector; - ctx.flow = {}; - ctx.flow.filter = (fn: any) => flow_filter(ctx, fn); -} +// watch: { +// (listener: (value: T, prev: T) => void): () => void; +// once(listener: (value: T, prev: T) => void): () => void; +// }; -function def_promisify(ctx: any) { - let resolve; - const promise = new Promise(r => (resolve = r)); - ['then', 'catch', 'finally'].forEach(prop => { - ctx[prop] = (promise as any)[prop].bind(promise); - }); - return resolve; -} +// flow: { +// filter(fn: (data: T) => any): Value>; +// }; +// }; -function wrap(target: any, set?: any, get?: any) { - const source_get = target[0]; - const source_set = target[1]; +// type Value = Callable & { +// 0: () => K; +// 1: (value: T) => void; +// val: T & K; +// get(): K; - let dest: any; - let dest_set: any; +// update: (fn: (state: K) => T) => void; +// sub: { +// (reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; +// once(reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; +// }; +// set(value: T): void; +// } & { +// wrap: { +//

(set: () => T, get: (data: K) => P): Value; +// (set: (data: M) => T, get: (data: K) => P): Value; +// (set: () => T): Value; +// (set: (data: M) => T): Value; - if (set) { - dest = dest_set = function (data?: any) { - const finish = untrack(); - const stack = stoppable_context; - stoppable_context = 1; - - try { - data = set(data); - if (stoppable_context === 1 || !stoppable_context[0]()) source_set(data); - } finally { - stoppable_context = stack; - finish(); - } - }; - } else if (source_set) { - dest = function (data?: any) { - source_set(data); - }; - } else { - dest = []; - } +// filter(fn: (data: T) => any): Value; +// }; - if (target.then) { - const methods = ['catch', 'finally']; - if (get) { - def_prop(dest, 'then', { - get() { - const promise = target.then(get); - return promise.then.bind(promise); - }, - }); - } else { - methods.push('then'); - } - methods.forEach(prop => { - def_prop(dest, prop, { - get: () => target[prop], - }); - }); - } +// view

(get: (data: K) => P): Value; +// select

(get: (data: K) => P): Selector

; +// select(): Selector; - if (target.reset) dest.reset = target.reset; - if (target.stop) target.stop = target; +// watch: { +// (listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; +// once(listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; +// }; +// reset(): void; - def_format( - dest, - get ? () => get(source_get()) : source_get, - dest_set || source_set, - !target.update, - target.then, - target.to - ); +// flow: { +// filter(fn: (data: K) => any): Value>; +// }; +// } & { +// [P in Exclude, number>]: never; +// } & +// [() => K, (value: T) => void]; - return dest; -} +// type Signal< +// T, +// K = T, +// X = {}, +// E = { +// reset(): void; +// update: (fn: (state: K) => T) => void; +// sub: { +// (reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; +// once(reactionable: Reactionable, fn: (data: K, value: S, prev: S) => T): () => void; +// }; +// set(value: T): void; +// } +// > = Callable & +// Pick, 'then' | 'catch' | 'finally'> & { +// 0: () => K; +// 1: (value: T) => void; +// readonly val: K; +// get(): K; +// } & { +// wrap: { +//

(set: () => T, get: (data: K) => P): Signal; +// (set: (data: M) => T, get: (data: K) => P): Signal; +// (set: () => T): Signal; +// (set: (data: M) => T): Signal; + +// filter(fn: (data: T) => any): Signal; +// }; + +// view

(get: (data: K) => P): Signal; +// select

(get: (data: K) => P): Selector

; +// select(): Selector; + +// watch: { +// (listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; +// once(listener: (value: K extends Ensurable ? P : K, prev: K) => void): () => void; +// }; + +// flow: { +// filter(fn: (data: K) => any): Signal>; +// }; +// } & E & +// X & +// { +// [P in Exclude, number>]: never; +// } & +// [() => K, (value: T) => void]; + +// type StopSignal = Signal< +// void, +// boolean, +// { +// stop(): void; +// set(): void; +// sub: { +// (reactionable: Reactionable): () => void; +// once(reactionable: Reactionable): () => void; +// }; +// }, +// {} +// >; +// type ReadySignal = Signal< +// T, +// K, +// { +// to(value: T): Signal; +// } +// >; + +// type Pool = K & { +// count: Selector; +// threads: Selector; +// pending: Selector; +// }; + +// function value(): Value; +// function value(init: T): Value; +// function value(init?: any): any { +// const [get, set] = box(init) as any; +// def_format(set, get, set); +// set.reset = () => set(init); +// return set; +// } -function flow_filter(target: Reactionable, fn: (data: T) => boolean) { - const f = (target as any).then ? signal() : value(); - on(target, v => { - if (fn(v)) f(v); - }); - return f; -} +// function selector(body: () => T): Selector { +// const [get, free] = sel(body); +// const h = [] as any; +// def_format(h, get); +// h.free = free; +// return h; +// } +// function signal(): Signal>; +// function signal(init: T): Signal; +// function signal(init?: any) { +// let resolve: any; +// const [get, set] = box([init]); + +// const fn = function (data: any) { +// const ready = resolve; +// resolve = def_promisify(fn); +// set([data]); +// ready(data); +// }; + +// resolve = def_promisify(fn); +// def_format(fn, () => get()[0], fn, 0, 1); +// fn.reset = () => set([init]); +// return fn as any; +// } -function stoppable(): StopSignal { - if (!stoppable_context) throw new Error('Parent context not found'); - if (stoppable_context === 1) stoppable_context = stop_signal(); - return stoppable_context; -} +// signal.stop = stop_signal; +// signal.ready = ready; +// signal.from = signal_from; +// signal.combine = signal_combine; + +// function ready(): ReadySignal>; +// function ready(init: T): ReadySignal; +// function ready(init?: any) { +// let resolved = 0; +// let resolve: any; +// const [get, set] = box([init]); + +// const fn = function (data: any) { +// if (!resolved) { +// resolved = 1; +// set([data]); +// resolve(data); +// } +// }; + +// resolve = def_promisify(fn); +// def_format(fn, () => get()[0], fn, is_stop_signal, 1, 1); + +// if (!is_stop_signal) { +// fn.reset = () => { +// resolve = def_promisify(fn); +// resolved = 0; +// set([init]); +// }; +// } + +// return fn as any; +// } -function pool Promise>(body: K): Pool { - const threads = value([]); - const count = threads.select(t => t.length); - const pending = count.select(c => c > 0); +// ready.flag = () => ready(false).to(true); +// ready.from = ready_from; +// ready.resolved = ready_resolved; - function run(this: any) { - const stop = stop_signal(); - isolate(threads.sub.once(stop, t => t.filter(ctx => ctx !== stop))); - threads.update(t => t.concat(stop as any)); +// function ready_from(source: Reactionable): ReadySignal { +// const fn = (source as any)[0] || (source as any); +// const dest = ready(fn()); +// on(source, dest); +// return dest as any; +// } - const stack = stoppable_context; - stoppable_context = stop; +// function ready_resolved(): ReadySignal; +// function ready_resolved(value: T): ReadySignal; +// function ready_resolved(value?: any): any { +// const r = ready(value); +// r(value); +// return r; +// } - let ret; - try { - ret = (body as any).apply(this, arguments); - } finally { - stoppable_context = stack; +// function signal_from(source: Reactionable): Signal { +// const fn = (source as any)[0] || (source as any); +// const dest = signal(fn()); +// on(source, dest); +// return dest as any; +// } - if (ret && ret.finally) { - ret.finally(stop); - } else { - stop(); - } - } - return ret; - } - run.count = count; - run.threads = threads; - run.pending = pending; +// function signal_combine(): Signal<[]>; +// function signal_combine(a: Reactionable): Signal<[A]>; +// function signal_combine(a: Reactionable, b: Reactionable): Signal<[A, B]>; +// function signal_combine( +// a: Reactionable, +// b: Reactionable, +// c: Reactionable +// ): Signal<[A, B, C]>; +// function signal_combine( +// a: Reactionable, +// b: Reactionable, +// c: Reactionable, +// d: Reactionable +// ): Signal<[A, B, C, D]>; +// function signal_combine( +// a: Reactionable, +// b: Reactionable, +// c: Reactionable, +// d: Reactionable, +// e: Reactionable +// ): Signal<[A, B, C, D, E]>; +// function signal_combine(...sources: any): any { +// const get = () => sources.map((src: any) => (src[0] || src)()); +// const dest = signal(get()); +// on([get], dest); +// return dest as any; +// } - return run as any; -} +// function stop_signal(): StopSignal { +// is_stop_signal = 1; +// try { +// const ctx = ready.flag() as any; +// return (ctx.stop = ctx); +// } finally { +// is_stop_signal = 0; +// } +// } -function on( - target: Reactionable>, - listener: (value: T, prev: Ensurable) => void -): () => void; -function on(target: Reactionable, listener: (value: T, prev: T) => void): () => void; -function on(target: any, listener: (value: any, prev?: any) => void): () => void { - const sync_mode = is_sync; - let free: (() => void) | undefined; +// function def_format( +// ctx: any, +// get: any, +// set?: any, +// no_update?: any, +// readonly_val?: any, +// has_to?: any +// ) { +// if (!Array.isArray(ctx)) { +// ctx[Symbol.iterator] = function* () { +// yield get; +// if (set) yield set; +// }; +// } +// ctx[0] = get; +// ctx.get = get; + +// const val_prop = { get } as any; +// if (set) { +// ctx[1] = set; +// if (!no_update) { +// ctx.update = (fn: any) => set(fn(get())); +// } +// ctx.set = set; +// if (!readonly_val) val_prop.set = set; + +// ctx.sub = (s: any, fn: any) => on(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : const_undef)); +// ctx.sub.once = (s: any, fn: any) => +// once(s, (v, v_prev) => set(fn ? fn(get(), v, v_prev) : const_undef)); +// } +// def_prop(ctx, key, val_prop); + +// if (has_to) { +// ctx.to = (value: any) => (wrap as any)(ctx, () => value); +// } +// if (set) { +// ctx.wrap = (set: any, get: any) => wrap(ctx, set, get); +// ctx.wrap.filter = (fn: any) => wrap(ctx, (v: any) => (fn(v) ? v : stoppable().stop())); +// } +// ctx.view = (get: any) => wrap(ctx, 0, get); +// ctx.watch = (fn: any) => on(ctx, fn); +// ctx.watch.once = (fn: any) => once(ctx, fn); + +// ctx.select = (fn: any) => selector(fn ? () => fn(get()) : get); + +// ctx.flow = {}; +// ctx.flow.filter = (fn: any) => flow_filter(ctx, fn); +// } - is_sync = 0; +// function def_promisify(ctx: any) { +// let resolve; +// const promise = new Promise(r => (resolve = r)); +// ['then', 'catch', 'finally'].forEach(prop => { +// ctx[prop] = (promise as any)[prop].bind(promise); +// }); +// return resolve; +// } - if (target[0]) { - target = target[0]; // box or selector or custom reactive - } else { - [target, free] = sel(target); - } +// function wrap(target: any, set?: any, get?: any) { +// const source_get = target[0]; +// const source_set = target[1]; + +// let dest: any; +// let dest_set: any; + +// if (set) { +// dest = dest_set = function (data?: any) { +// const finish = untrack(); +// const stack = stoppable_context; +// stoppable_context = 1; + +// try { +// data = set(data); +// if (stoppable_context === 1 || !stoppable_context[0]()) source_set(data); +// } finally { +// stoppable_context = stack; +// finish(); +// } +// }; +// } else if (source_set) { +// dest = function (data?: any) { +// source_set(data); +// }; +// } else { +// dest = []; +// } + +// if (target.then) { +// const methods = ['catch', 'finally']; +// if (get) { +// def_prop(dest, 'then', { +// get() { +// const promise = target.then(get); +// return promise.then.bind(promise); +// }, +// }); +// } else { +// methods.push('then'); +// } +// methods.forEach(prop => { +// def_prop(dest, prop, { +// get: () => target[prop], +// }); +// }); +// } + +// if (target.reset) dest.reset = target.reset; +// if (target.stop) target.stop = target; + +// def_format( +// dest, +// get ? () => get(source_get()) : source_get, +// dest_set || source_set, +// !target.update, +// target.then, +// target.to +// ); + +// return dest; +// } - let value: any; +// function flow_filter(target: Reactionable, fn: (data: T) => boolean) { +// const f = (target as any).then ? signal() : value(); +// on(target, v => { +// if (fn(v)) f(v); +// }); +// return f; +// } - const [run, stop] = expr(target, () => { - const prev = value; - listener((value = run()), prev); - }); - value = run(); - const unsub = () => { - if (free) free(); - stop(); - }; - un(unsub); - if (sync_mode) listener(value); - return unsub; -} -on.once = once; +// function stoppable(): StopSignal { +// if (!stoppable_context) throw new Error('Parent context not found'); +// if (stoppable_context === 1) stoppable_context = stop_signal(); +// return stoppable_context; +// } -function once( - target: Reactionable>, - listener: (value: T, prev: Ensurable) => void -): () => void; -function once(target: Reactionable, listener: (value: T, prev: T) => void): () => void; -function once(target: any, listener: (value: any, prev?: any) => void): () => void { - const unsub = on(target, (value, prev) => { - try { - listener(value, prev); - } finally { - unsub(); - } - }); - return unsub; -} +// function pool Promise>(body: K): Pool { +// const threads = value([]); +// const count = threads.select(t => t.length); +// const pending = count.select(c => c > 0); + +// function run(this: any) { +// const stop = stop_signal(); +// isolate(threads.sub.once(stop, t => t.filter(ctx => ctx !== stop))); +// threads.update(t => t.concat(stop as any)); + +// const stack = stoppable_context; +// stoppable_context = stop; + +// let ret; +// try { +// ret = (body as any).apply(this, arguments); +// } finally { +// stoppable_context = stack; + +// if (ret && ret.finally) { +// ret.finally(stop); +// } else { +// stop(); +// } +// } +// return ret; +// } +// run.count = count; +// run.threads = threads; +// run.pending = pending; + +// return run as any; +// } -function sync(target: Reactionable, listener: (value: T, prev: T) => void): () => void { - is_sync = 1; - return on(target, listener); -} +// function on( +// target: Reactionable>, +// listener: (value: T, prev: Ensurable) => void +// ): () => void; +// function on(target: Reactionable, listener: (value: T, prev: T) => void): () => void; +// function on(target: any, listener: (value: any, prev?: any) => void): () => void { +// const sync_mode = is_sync; +// let free: (() => void) | undefined; + +// is_sync = 0; + +// if (target[0]) { +// target = target[0]; // box or selector or custom reactive +// } else { +// [target, free] = sel(target); +// } + +// let value: any; + +// const [run, stop] = expr(target, () => { +// const prev = value; +// listener((value = run()), prev); +// }); +// value = run(); +// const unsub = () => { +// if (free) free(); +// stop(); +// }; +// un(unsub); +// if (sync_mode) listener(value); +// return unsub; +// } +// on.once = once; + +// function once( +// target: Reactionable>, +// listener: (value: T, prev: Ensurable) => void +// ): () => void; +// function once(target: Reactionable, listener: (value: T, prev: T) => void): () => void; +// function once(target: any, listener: (value: any, prev?: any) => void): () => void { +// const unsub = on(target, (value, prev) => { +// try { +// listener(value, prev); +// } finally { +// unsub(); +// } +// }); +// return unsub; +// } -function cycle(body: () => void) { - const iter = () => { - const stack = stoppable_context; - stoppable_context = stop_signal(); - isolate(once(stoppable_context, stop)); - try { - run(); - } finally { - stoppable_context = stack; - } - }; +// function sync(target: Reactionable, listener: (value: T, prev: T) => void): () => void { +// is_sync = 1; +// return on(target, listener); +// } - const [run, stop] = expr(body, iter); - iter(); - return un(stop); -} -function isolate(): () => () => void; -function isolate(fn: () => void): () => void; -function isolate(fn?: any) { - if (fn) { - if (context_unsubs) context_unsubs = context_unsubs.filter((i: any) => i !== fn); - return fn; - } - const stack = context_unsubs; - context_unsubs = []; - return () => { - const unsubs = context_unsubs; - context_unsubs = stack; - return () => { - if (unsubs) call_fns_array(unsubs); - }; - }; -} +// function cycle(body: () => void) { +// const iter = () => { +// const stack = stoppable_context; +// stoppable_context = stop_signal(); +// isolate(once(stoppable_context, stop)); +// try { +// run(); +// } finally { +// stoppable_context = stack; +// } +// }; + +// const [run, stop] = expr(body, iter); +// iter(); +// return un(stop); +// } +// function isolate(): () => () => void; +// function isolate(fn: () => void): () => void; +// function isolate(fn?: any) { +// if (fn) { +// if (context_unsubs) context_unsubs = context_unsubs.filter((i: any) => i !== fn); +// return fn; +// } +// const stack = context_unsubs; +// context_unsubs = []; +// return () => { +// const unsubs = context_unsubs; +// context_unsubs = stack; +// return () => { +// if (unsubs) call_fns_array(unsubs); +// }; +// }; +// } @@ -1511,197 +1759,191 @@ function isolate(fn?: any) { -function initial(data: any): void { - initial_data = data; -} -function mock(target: (new (init?: any) => M) | ((init?: any) => M), mocked: M): M { - shareds.set(target, mocked); - return mocked; -} +// function initial(data: any): void { +// initial_data = data; +// } -function unmock( - target: (new (init?: any) => any) | ((init?: any) => any), - ...targets: ((new (init?: any) => any) | ((init?: any) => any))[] -) { - targets.concat(target).forEach(target => shareds.delete(target)); -} +// function mock(target: (new (init?: any) => M) | ((init?: any) => M), mocked: M): M { +// shareds.set(target, mocked); +// return mocked; +// } -function shared(target: (new (init?: any) => M) | ((init?: any) => M)): M { - let instance = shareds.get(target); - if (!instance) { - const h = inst(target, [initial_data]); - instance = h[0]; - shared_unsubs.push(h[1]); - shareds.set(target, instance); - } - return instance; -} +// function unmock( +// target: (new (init?: any) => any) | ((init?: any) => any), +// ...targets: ((new (init?: any) => any) | ((init?: any) => any))[] +// ) { +// targets.concat(target).forEach(target => shareds.delete(target)); +// } -function inst( - target: (new (...args: K) => M) | ((...args: K) => M), - args: K, - hooks_available?: any -): [M, () => void, (() => void)[]] { - let instance, unsub, hooks; - const collect = isolate(); - const track = untrack(); - const stack = context_hooks; - context_hooks = []; - try { - instance = - typeof target.prototype === 'undefined' - ? (target as any)(...args) - : new (target as any)(...args); - if (!hooks_available && context_hooks.length > 0) throw_hook_error(); - } finally { - unsub = collect(); - track(); - hooks = context_hooks; - context_hooks = stack; - } - return [instance, unsub, hooks]; -} +// function shared(target: (new (init?: any) => M) | ((init?: any) => M)): M { +// let instance = shareds.get(target); +// if (!instance) { +// const h = inst(target, [initial_data]); +// instance = h[0]; +// shared_unsubs.push(h[1]); +// shareds.set(target, instance); +// } +// return instance; +// } + +// function inst( +// target: (new (...args: K) => M) | ((...args: K) => M), +// args: K, +// hooks_available?: any +// ): [M, () => void, (() => void)[]] { +// let instance, unsub, hooks; +// const collect = internal_isolate(); +// const track = internal_untrack(); +// const stack = context_hooks; +// context_hooks = []; +// try { +// instance = +// typeof target.prototype === 'undefined' +// ? (target as any)(...args) +// : new (target as any)(...args); +// if (!hooks_available && context_hooks.length > 0) throw_hook_error(); +// } finally { +// unsub = collect(); +// track(); +// hooks = context_hooks; +// context_hooks = stack; +// } +// return [instance, unsub, hooks]; +// } // function throw_hook_error() { // throw new Error('Hook section available only at useLocal'); // } -function hook(fn: () => void): void { - if (!context_hooks) throw_hook_error(); - fn && context_hooks.push(fn); -} +// function hook(fn: () => void): void { +// if (!context_hooks) throw_hook_error(); +// fn && context_hooks.push(fn); +// } // function call_array(arr: (() => void)[]) { // arr.forEach(fn => fn()); // } -function get_scope_context(): Context { - return scope_context ? scope_context : (scope_context = (createContext as any)()); -} - -function useForceUpdate() { - return useReducer(() => [], [])[1] as () => void; -} - -function observe(FunctionComponent: T): T { - return function (this: any) { - const forceUpdate = useForceUpdate(); - const ref = useRef<[T, () => void]>(); - if (!ref.current) ref.current = expr(FunctionComponent, forceUpdate); - useEffect(() => ref.current![1], []); - - const stack = is_observe; - is_observe = 1; - try { - return (ref.current[0] as any).apply(this, arguments); - } finally { - is_observe = stack; - } - } as any; -} - -const Scope: FC = ({ children }) => { - const h = useMemo(() => [new Map(), [], []], []) as any; - useEffect(() => () => call_fns_array(h[1]), []); - return createElement(get_scope_context().Provider, { value: h }, children); -}; - -function useScoped(target: (new (init?: any) => M) | ((init?: any) => M)): M { - const context_data = useContext(get_scope_context()); - if (!context_data) { - throw new Error('"Scope" parent component didn\'t find'); - } - - let instance; - if (context_data[0].has(target)) { - instance = context_data[0].get(target); - } else { - const h = inst(target, [initial_data]); - context_data[0].set(target, (instance = h[0])); - context_data[1].push(h[1]); - } +// function get_scope_context(): Context { +// return scope_context ? scope_context : (scope_context = (createContext as any)()); +// } - return instance; -} +// function useForceUpdate() { +// return useReducer(() => [], [])[1] as () => void; +// } -function useLocal( - target: (new (...args: T) => M) | ((...args: T) => M), - deps = ([] as unknown) as T -): M { - const h = useMemo(() => { - const i = inst(target, deps, 1); - const call_hooks = () => call_fns_array(i[2]); - return [i[0], () => i[1], call_hooks] as any; - }, deps); - h[2](); +// function observe(FunctionComponent: T): T { +// return function (this: any) { +// const forceUpdate = useForceUpdate(); +// const ref = useRef<[T, () => void]>(); +// if (!ref.current) ref.current = expr(FunctionComponent, forceUpdate); +// useEffect(() => ref.current![1], []); + +// const stack = is_observe; +// is_observe = 1; +// try { +// return (ref.current[0] as any).apply(this, arguments); +// } finally { +// is_observe = stack; +// } +// } as any; +// } - useEffect(h[1], [h]); - return h[0]; -} +// const Scope: FC = ({ children }) => { +// const h = useMemo(() => [new Map(), [], []], []) as any; +// useEffect(() => () => call_fns_array(h[1]), []); +// return createElement(get_scope_context().Provider, { value: h }, children); +// }; + +// function useScoped(target: (new (init?: any) => M) | ((init?: any) => M)): M { +// const context_data = useContext(get_scope_context()); +// if (!context_data) { +// throw new Error('"Scope" parent component didn\'t find'); +// } + +// let instance; +// if (context_data[0].has(target)) { +// instance = context_data[0].get(target); +// } else { +// const h = inst(target, [initial_data]); +// context_data[0].set(target, (instance = h[0])); +// context_data[1].push(h[1]); +// } + +// return instance; +// } -function useValue(target: Reactionable, deps: any[] = []): T { - const forceUpdate = is_observe || useForceUpdate(); - const h = useMemo(() => { - if (!target) return [target, () => {}]; - if ((target as any)[0]) target = (target as any)[0]; // box or selector or custom reactive +// function useLocal( +// target: (new (...args: T) => M) | ((...args: T) => M), +// deps = ([] as unknown) as T +// ): M { +// const h = useMemo(() => { +// const i = inst(target, deps, 1); +// const call_hooks = () => call_fns_array(i[2]); +// return [i[0], () => i[1], call_hooks] as any; +// }, deps); +// h[2](); + +// useEffect(h[1], [h]); +// return h[0]; +// } - if (typeof target === 'function') { - if (is_observe) { - return [target, 0, 1]; - } else { - const [run, stop] = expr(target as any, () => { - forceUpdate(); - run(); - }); - run(); - return [target, () => stop, 1]; - } - } else { - return [target as any, () => {}]; - } - }, deps); +// function useValue(target: Reactionable, deps: any[] = []): T { +// const forceUpdate = is_observe || useForceUpdate(); +// const h = useMemo(() => { +// if (!target) return [target, () => {}]; +// if ((target as any)[0]) target = (target as any)[0]; // box or selector or custom reactive + +// if (typeof target === 'function') { +// if (is_observe) { +// return [target, 0, 1]; +// } else { +// const [run, stop] = expr(target as any, () => { +// forceUpdate(); +// run(); +// }); +// run(); +// return [target, () => stop, 1]; +// } +// } else { +// return [target as any, () => {}]; +// } +// }, deps); + +// is_observe || useEffect(h[1], [h]); +// return h[2] ? h[0]() : h[0]; +// } - is_observe || useEffect(h[1], [h]); - return h[2] ? h[0]() : h[0]; -} -function free() { - try { - call_fns_array(shared_unsubs); - } finally { - shareds.clear(); - initial_data = const_undef; - } -} -const obj_def_box_prop = (o: any, p: string | number | symbol, init?: any): any => { - const b = box(init && init()); - obj_def_prop(o, p, { get: b[0], set: b[1] }); -} +// const obj_def_box_prop = (o: any, p: string | number | symbol, init?: any): any => { +// const b = box(init && init()); +// obj_def_prop(o, p, { get: b[0], set: b[1] }); +// } -function prop(_proto: any, key: any, descriptor?: any): any { - const initializer = descriptor?.initializer; - return { - get() { - obj_def_box_prop(this, key, initializer); - return this[key]; - }, - set(value: any) { - obj_def_box_prop(this, key, initializer); - this[key] = value; - }, - }; -} +// function prop(_proto: any, key: any, descriptor?: any): any { +// const initializer = descriptor?.initializer; +// return { +// get() { +// obj_def_box_prop(this, key, initializer); +// return this[key]; +// }, +// set(value: any) { +// obj_def_box_prop(this, key, initializer); +// this[key] = value; +// }, +// }; +// } -function cache(_proto: any, key: any, descriptor: any): any { - return { - get() { - const [get] = sel(descriptor.get); - obj_def_prop(this, key, { get }); - return this[key]; - }, - }; -} +// function cache(_proto: any, key: any, descriptor: any): any { +// return { +// get() { +// const [get] = sel(descriptor.get); +// obj_def_prop(this, key, { get }); +// return this[key]; +// }, +// }; +// } diff --git a/tests/wrap.test.ts b/tests/builder-5/pre.test.ts similarity index 53% rename from tests/wrap.test.ts rename to tests/builder-5/pre.test.ts index e06c0214..69042ad2 100644 --- a/tests/wrap.test.ts +++ b/tests/builder-5/pre.test.ts @@ -1,14 +1,11 @@ -import { signal, stoppable, selector, on } from '../src'; +import { _signal as signal, _on as on } from '../../src'; test('should work basic operations with wrap and signal', async () => { const spy = jest.fn(); const s = signal(0); - const s_1 = s.wrap( - (v: string) => parseInt(v + v), - (g: number) => g + 77 - ); - const s_2 = s_1.wrap((v: number) => '' + (v + 1)); + const s_1 = s.pre((v: string) => parseInt(v + v)).view((g: number) => g + 77); + const s_2 = s_1.pre((v: number) => '' + (v + 1)); expect(s_1.val).toBe(77); s_1('1'); @@ -16,27 +13,18 @@ test('should work basic operations with wrap and signal', async () => { s_2(10); expect(s_2.val).toBe(1188); - const [get, set] = s_2; + const {get, set} = s_2; on(s_2, spy); set(10); expect(spy).toBeCalledWith(1188, 1188); expect(get()).toBe(1188); }); -test('should work basic operations with wrap and stoppable', async () => { +test('should work basic operations with wrap and pre.filter', async () => { const spy = jest.fn(); const s = signal(0); - const s_1 = s.wrap((v: number) => { - const stop = stoppable(); - - expect((stop as any).reset).not.toBeDefined(); - - if (v === 10 || v === 15) { - stop(); - } - return v; - }); + const s_1 = s.pre.filter((v) => v !== 10 && v !== 15); on(s_1, spy); diff --git a/tests/watch.test.ts b/tests/builder-5/to.test.ts similarity index 68% rename from tests/watch.test.ts rename to tests/builder-5/to.test.ts index 6e6e3e88..550fbe55 100644 --- a/tests/watch.test.ts +++ b/tests/builder-5/to.test.ts @@ -1,16 +1,15 @@ -import { signal } from '../src'; +import { _signal as signal } from '../../src'; test('should work basic operations with watch and wrapped signal', async () => { const spy = jest.fn(); const s = signal(0); - const s_1 = s.wrap( - (v: string) => parseInt(v + v), - (g: number) => g + 77 - ); - const s_2 = s_1.wrap((v: number) => '' + (v + 1)); + const s_1 = s + .pre((v: string) => parseInt(v + v)) + .view((g: number) => g + 77); + const s_2 = s_1.pre((v: number) => '' + (v + 1)); - s_2.watch(spy); + s_2.to(spy); s_2(10); expect(spy).toBeCalledWith(1188, 77); @@ -21,7 +20,7 @@ test('should work basic operations with watch once and signal', async () => { const spy = jest.fn(); const s = signal(0); - s.watch.once(spy); + s.to.once(spy); expect(spy).toBeCalledTimes(0); s(10); diff --git a/tests/builder-5/trigger.test.ts b/tests/builder-5/trigger.test.ts index 87936520..bd5b3146 100644 --- a/tests/builder-5/trigger.test.ts +++ b/tests/builder-5/trigger.test.ts @@ -134,11 +134,11 @@ test('should work _signal.trigger wrapped', async () => { // test('should work ready resolved', async () => { // const r = ready.resolved(1); // expect(r.val).toBe(1); -// expect(await r).toBe(1); +// expect(await r.promise).toBe(1); // }); // test('should work ready with undefined resolved', async () => { // const r = ready.resolved(); // expect(r.val).toBe(void 0); -// expect(await r).toBe(void 0); +// expect(await r.promise).toBe(void 0); // }); diff --git a/tests/view.test.ts b/tests/builder-5/view.test.ts similarity index 85% rename from tests/view.test.ts rename to tests/builder-5/view.test.ts index bbf29ad9..3289da41 100644 --- a/tests/view.test.ts +++ b/tests/builder-5/view.test.ts @@ -1,4 +1,4 @@ -import { signal, stoppable, selector, on, value, transaction } from '../src'; +import { _signal as signal, _on as on, _value as value, _transaction as transaction } from '../../src'; test('should work basic operations with view methods for value', () => { const spy = jest.fn(); @@ -6,7 +6,7 @@ test('should work basic operations with view methods for value', () => { const v_1 = v.view(v => v + v); on(v_1, spy); - const commit = transaction(); + const commit = transaction.unsafe(); v(1); v_1.update(v => v + v); commit(); diff --git a/tests/builder-6.test.ts b/tests/builder-6.test.ts index 6ca2e416..c8209306 100644 --- a/tests/builder-6.test.ts +++ b/tests/builder-6.test.ts @@ -1,4 +1,4 @@ -import { _value, _selector, _transaction, cycle, _signal } from '../src'; +import { _value, _selector, _transaction, _cycle as cycle, _signal } from '../src'; test('should work _value with call, get, set, update, sync', () => { const spy = jest.fn(); diff --git a/tests/stoppable.test.ts b/tests/contextual-stop.test.ts similarity index 75% rename from tests/stoppable.test.ts rename to tests/contextual-stop.test.ts index 8b44d093..35308270 100644 --- a/tests/stoppable.test.ts +++ b/tests/contextual-stop.test.ts @@ -1,13 +1,10 @@ -import { cycle, on, stoppable, value } from '../src'; +import { _cycle as cycle, _value as value, _contextual as contextual } from '../src'; test('should work stoppable stop method', () => { const spy = jest.fn(); - const a = value(0).wrap((v: number) => { - if (v % 2) stoppable().stop(); - return v; - }); - a.watch(spy); + const a = value(0).pre.filter((v: number) => !(v % 2)); + a.to(spy); a(1); a(2); @@ -22,10 +19,10 @@ test('should work stoppable in cycle not first iteration', () => { const spy = jest.fn(); const a = value(1); - a.watch(spy); + a.to(spy); cycle(() => { a.val += a.val; - if (a.val > 10) stoppable().stop(); + if (a.val > 10) contextual.stop(); }); expect(spy).toHaveBeenNthCalledWith(4, 16, 8); @@ -36,10 +33,10 @@ test('should work stoppable in cycle first iteration', () => { const spy = jest.fn(); const a = value(1); - a.watch(spy); + a.to(spy); cycle(() => { - stoppable().stop(); + contextual.stop(); a.update(v => v + v); }); @@ -49,6 +46,6 @@ test('should work stoppable in cycle first iteration', () => { test('should throw exception if run stoppable outside of stoppable context', () => { expect(() => { - stoppable(); + contextual.stop; }).toThrow('Parent context not found'); }); diff --git a/tests/isolate.test.ts b/tests/isolate.test.ts index 0c82a2a6..67a00147 100644 --- a/tests/isolate.test.ts +++ b/tests/isolate.test.ts @@ -1,4 +1,4 @@ -import { isolate, shared, free, un } from '../src'; +import { _isolate as isolate, shared, free, un } from '../src'; test('should work basic operations with isolate', async () => { // const destr_1 = jest.fn(); diff --git a/tests/on.test.ts b/tests/on.test.ts index f9b4564c..637c799d 100644 --- a/tests/on.test.ts +++ b/tests/on.test.ts @@ -1,4 +1,4 @@ -import { prop, cache, on, value } from '../src'; +import { prop, cache, _on as on, _value as value, _isolate as isolate } from '../src'; test('should work basic operations with prop, cache and on', () => { const spy = jest.fn(); @@ -21,12 +21,12 @@ test('should cache return value in on', () => { const spy = jest.fn(); const a = value(0); - on(() => Math.floor(a[0]() / 2), spy); + on(() => Math.floor(a.val / 2), spy); - a[1](1); + a.set(1); expect(spy).toBeCalledTimes(0); - a[1](2); + a.set(2); expect(spy).toHaveBeenNthCalledWith(1, 1, 0); }); @@ -38,7 +38,7 @@ test('should work stop on subscription', () => { () => a.val, () => stop() ); - const stop = on(() => a.val, spy); + const stop = isolate(() => on(() => a.val, spy)); on( () => a.val, () => stop() diff --git a/tests/pool.test.ts b/tests/pool.test.ts index e211b25e..a837689d 100644 --- a/tests/pool.test.ts +++ b/tests/pool.test.ts @@ -1,4 +1,4 @@ -import { pool, stoppable } from '../src'; +import { pool, _contextual as contextual } from '../src'; import { delay } from './lib'; test('should work basic operations with pool', async () => { @@ -7,10 +7,9 @@ test('should work basic operations with pool', async () => { const p = pool(async () => { const id = i++; - const stop = stoppable(); - spy(stop.val, id); + spy(id); await delay(10); - spy(stop.val, id); + spy(id); }); expect(p.pending.val).toBe(false); @@ -21,8 +20,8 @@ test('should work basic operations with pool', async () => { expect(p.count.val).toBe(2); expect(spy).toBeCalledTimes(2); - expect(spy).toHaveBeenNthCalledWith(1, false, 0); - expect(spy).toHaveBeenNthCalledWith(2, false, 1); + expect(spy).toHaveBeenNthCalledWith(1, 0); + expect(spy).toHaveBeenNthCalledWith(2, 1); p.threads.val[0](); @@ -33,8 +32,8 @@ test('should work basic operations with pool', async () => { await delay(20); expect(spy).toBeCalledTimes(4); - expect(spy).toHaveBeenNthCalledWith(3, true, 0); - expect(spy).toHaveBeenNthCalledWith(4, false, 1); + expect(spy).toHaveBeenNthCalledWith(3, 0); + expect(spy).toHaveBeenNthCalledWith(4, 1); expect(p.pending.val).toBe(false); expect(p.count.val).toBe(0); @@ -49,7 +48,7 @@ test('should work correct pool with not async function', async () => { }); expect(p.pending.val).toBe(false); - p.pending.watch(spy); + p.pending.to(spy); expect(p()).toBe(10); expect(p.threads.val).toEqual([]); diff --git a/tests/prop.test.ts b/tests/prop.test.ts index 3251ba7b..74cacac3 100644 --- a/tests/prop.test.ts +++ b/tests/prop.test.ts @@ -1,4 +1,4 @@ -import { prop, on } from '../src'; +import { prop, _on as on } from '../src'; test('should work prop decorator with first set', () => { const spy = jest.fn(); diff --git a/tests/sync.test.ts b/tests/sync.test.ts index fb02411f..ffa0d472 100644 --- a/tests/sync.test.ts +++ b/tests/sync.test.ts @@ -1,4 +1,4 @@ -import { prop, cache, sync, value } from '../src'; +import { prop, cache, _sync as sync, _value as value } from '../src'; test('should work basic operations with prop, cache and sync', () => { const spy = jest.fn(); @@ -11,7 +11,7 @@ test('should work basic operations with prop, cache and sync', () => { const a = new A(); sync(() => a.b, spy); expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenNthCalledWith(1, 11); + expect(spy).toHaveBeenNthCalledWith(1, 11, void 0); a.a += 10; expect(spy).toHaveBeenNthCalledWith(2, 21, 11); @@ -22,12 +22,12 @@ test('should cache return value in sync', () => { const spy = jest.fn(); const a = value(0); - sync(() => Math.floor(a[0]() / 2), spy); + sync(() => Math.floor(a.val / 2), spy); expect(spy).toBeCalledTimes(1); - expect(spy).toHaveBeenNthCalledWith(1, 0); + expect(spy).toHaveBeenNthCalledWith(1, 0, void 0); - a[1](1); - a[1](2); + a.set(1); + a.set(2); expect(spy).toHaveBeenNthCalledWith(2, 1, 0); expect(spy).toBeCalledTimes(2); }); diff --git a/tests/use-value.test.tsx b/tests/use-value.test.tsx index bf965c51..0c17caee 100644 --- a/tests/use-value.test.tsx +++ b/tests/use-value.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { mount } from 'enzyme'; -import { value, observe, useValue } from '../src'; +import { _value as value, observe, useValue } from '../src'; test('should work useValue function', () => { const spy = jest.fn(); @@ -9,7 +9,7 @@ test('should work useValue function', () => { function A() { const val = useValue(h); spy(val); - return From 465e233c9090930742ed0e1d641d3985c1fe9ee8 Mon Sep 17 00:00:00 2001 From: betula Date: Fri, 28 May 2021 11:55:24 +0700 Subject: [PATCH 077/100] feat: useJsx test cases added --- src/index.ts | 7 ++-- tests/use-jsx.test.tsx | 93 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/use-jsx.test.tsx diff --git a/src/index.ts b/src/index.ts index 742cf6c1..95653669 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import rb from 'reactive-box'; /* TODOs: - [] useJsx(() => {}, [...deps]?); + [] test cases for useJsx [] typings for builder [] documentation update @@ -81,6 +81,7 @@ type Value = { set: (value: I) => void; get: () => O; + val: I & O; select: any; update: any; } @@ -200,7 +201,7 @@ type UseValues = { } type UseJsx = { - (func: T, deps?: any[]): React.MemoExoticComponent; + (func: FC, deps?: any[]): React.MemoExoticComponent>; } @@ -1240,7 +1241,7 @@ const useValues: UseValues = (targets, deps):any => { }; const useJsx: UseJsx = (target, deps): any => ( - useMemo(() => observe(target), deps || []) + useMemo(() => observe(target as any), deps || []) ); diff --git a/tests/use-jsx.test.tsx b/tests/use-jsx.test.tsx new file mode 100644 index 00000000..80fd4ebb --- /dev/null +++ b/tests/use-jsx.test.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import { mount } from 'enzyme'; +import { value, useJsx } from '../src'; + +test('should work useJsx with no deps', () => { + const a = value(0); + const b = value(0); + + function A(props) { + const Body = useJsx<{ value: string }>(({ children, value }) => ( + <> + {a.val} + {b.val} + {value} + {children} + + )); + return ( + + - - - ); -}; - -const App = () => ( - <> - - - - - -); - -export default App; -``` - -For best possibilities use [realar babel plugin](https://github.com/betula/babel-plugin-realar), your code will be so beautiful to look like. - -But otherwise necessary to wrap all React function components that use reactive values inside to `observe` wrapper. [Try wrapped version on CodeSandbox](https://codesandbox.io/s/realar-counter-k9kmw?file=/src/App.tsx). - -### Access visibility levels - -The basic level of scopes for React developers is a **component level scope** (_for example `useState`, and other standard React hooks has that level_). - -Every React component instance has its own local state, which is saved every render for the component as long as the component is mounted. - -In the Realar ecosystem `useLocal` hook used to make components local state. - -```javascript -class CounterLogic { - @prop value = 0; - inc = () => this.value += 1 -} - -const Counter = () => { - const { value, inc } = useLocal(CounterLogic); - - return ( -

{value}

- ); -} - -export const App = () => ( - <> - - - -); -``` -[Play wrapped on CodeSandbox](https://codesandbox.io/s/realar-component-level-scope-classes-m0i10?file=/src/App.tsx) - -This feature can be useful for removing logic from the body of a component to keep that free of unnecessary code, and therefore cleaner. - -**context component level scope** - -```javascript -const Counter = () => { - const { value, inc } = useScoped(CounterLogic); - - return ( -

{value}

- ); -} - -export const App = () => ( - - - - - - - -); -``` - -[Play wrapped on CodeSandbox](https://codesandbox.io/s/realar-context-component-level-scope-classes-wivjv?file=/src/App.tsx) ### Signals @@ -221,13 +76,13 @@ If you making an instance of a class with a subscription in the constructor, tho Below other examples ```javascript -const add = signal(); +const add = signal(); const store = value(1); on(add, num => store.val += num); add(15); -console.log(store.val); // 16 +console.log(store.val); // console output: 16 ``` [Edit on RunKit](https://runkit.com/betula/6013af7649e8720019c9cf2a) @@ -236,18 +91,19 @@ An signal is convenient to use as a promise. ```javascript const fire = signal(); -const listen = async () => { +(async () => { for (;;) { - await fire; // await as a usual promise + await fire.promise; // await as a usual promise console.log('Fire'); } -} +})(); -listen(); setInterval(fire, 500); ``` [Edit on RunKit](https://runkit.com/betula/601e3b0056b62d001bfa391b) + + ### Core The abstraction of the core is an implementation of functional reactive programming on javascript and binding that with React. @@ -284,192 +140,149 @@ That component will be updated every time when new sum value is coming. The difference from exists an implementation of functional reactive programming (mobx) in Realar dependency collector provides the possibility to write in selectors and nested writable reactions. -Realar provides big possibility abstractions for reactive flow. We already know about reactive value container, reactive expressions, and subscribe mechanism. But also have synchronization between data, cycled reactions, cached selectors, and transactions. +Realar provides big possibility abstractions for reactive flow. We already know about reactive value container, reactive expressions, and subscribe mechanism. But also have synchronization between data, cycled reactions, cached selectors, transactions and etc. -### Low level usage -```javascript -const count = value(0); -const tick = () => count.val++; -setInterval(tick, 200); +### OOP Usage -const App = () => { - const value = useValue(count); - return ( -

{value}

- ) +Transparent functional reactive programming with classes, decorators and [babel jsx wrapper](https://github.com/betula/babel-plugin-realar) + +```javascript +class Ticker { + @prop count = 0 + tick = () => ++this.count; } + +const ticker = new Ticker(); +setInterval(ticker.tick, 200); + +const App = () => ( +

{ticker.count}

+) ``` -[Try on CodeSandbox](https://codesandbox.io/s/realar-ticker-functional-6s3mx?file=/src/App.tsx) +[Try wrapped version on CodeSandbox](https://codesandbox.io/s/realar-ticker-classes-c9819?file=/src/App.tsx) + +It looks likes very clear and natively, and you can start development knows only two functions. + +`prop`. Reactive value marker. Each reactive value has an immutable state. If the immutable state will update, all React components that depend on It will refresh. + +`shared`. One of the primary reasons for using state manager in your application is a shared state accessing, and using shared logic between scattered React components and any place of your code. ```javascript -import React from "react"; -import { value, useValue } from "realar"; +import React from 'react'; +import { prop, shared } from 'realar'; -const [get, set] = value(0); +class Counter { + @prop value = 0; -const next = () => get() + 1; + inc = () => this.value += 1; + dec = () => this.value -= 1; +} -const inc = () => set(next()); -const dec = () => set(get() - 1); +const sharedCounter = () => shared(Counter); -const Current = () => { - const value = useValue(get); - return

current: {value}

; +const Count = () => { + const { value } = sharedCounter(); + return

{value}

; }; -const Next = () => { - const value = useValue(next); - return

next: {value}

; +const Buttons = () => { + const { inc, dec } = sharedCounter(); + return ( + <> + + + + ); }; const App = () => ( <> - - - - - + + + + ); export default App; ``` -[Try on CodeSandbox](https://codesandbox.io/s/realar-pure-counter-1ue4h?file=/src/App.tsx). - -### API -**value** - -The first abstraction of Realar is reactive container - `value`. -The `value` is a place where your store some data as an immutable struct. -When you change value (rewrite to a new immutable struct) all who depend on It will be updated synchronously. - -For create new value we need `value` function from `realar`, and initial value that will store in reactive container. -The call of `value` function returns array of two functions. -- The first is value getter. -- The second one is necessary for save new value to reactive container. - -```javascript -const [get, set] = value(0); - -set(get() + 1); - -console.log(get()); // 1 -``` -[Edit on RunKit](https://runkit.com/betula/6013af7649e8720019c9cf2a) +For best possibilities use [babel jsx wrapper](https://github.com/betula/babel-plugin-realar), your code will be so beautiful to look like. -In that example -- for a first we created `value` container for number with initial zero; -- After that, we got the value, and set to its value plus one; -- Let's print the result to the developer console, that will is one. - -We learned how to create a value, set, and get it. - -**on** - -The next basic abstraction is expression. -Expression is a function that read reactive boxes or selectors. It can return value and write reactive values inside. - -We can subscribe to change any reactive expression using `on` function _(which also works with signal)_. - -```javascript -const [get, set] = value(0); - -const next = () => get() + 1; - -on(next, (val, prev) => console.log(val, prev)); +But otherwise necessary to wrap all React function components that use reactive values inside to `observe` wrapper. [Try wrapped version on CodeSandbox](https://codesandbox.io/s/realar-counter-k9kmw?file=/src/App.tsx). -set(5); // We will see 6 and 1 in developer console output, It are new and previous value -``` -[Edit on RunKit](https://runkit.com/betula/6013ea214e0cf9001ac18e71) +#### React component access visibility level -In that example expression is `next` function, because It get value and return that plus one. +The basic level of scopes for React developers is a component level scope (_for example `useState`, and other standard React hooks has that level_). -**selector** +Every React component instance has its own local state, which is saved every render for the component as long as the component is mounted. -Necessary for making high-cost calculations and cache them for many times of accessing without changing source dependencies. And for downgrade (selection from) your hierarchical store. +In the Realar ecosystem `useLocal` hook used to make components local stateful logic. ```javascript -const store = value({ - address: { - city: 'NY' - } -}); - -const address = selector(() => store.val.address); - -on(address, ({ city }) => console.log(city)); // Subscribe to address selector +class CounterLogic { + @prop value = 0; + inc = () => this.value += 1 +} -console.log(address.val.city); // Log current value of address selector +const Counter = () => { + const { value, inc } = useLocal(CounterLogic); -store.update(state => ({ - ...state, - user: {} -})); -// Store changed but non reaction from address selector + return ( +

{value}

+ ); +} -store.update(state => ({ - ...state, - address: { - city: 'LA' - } -})); -// We can see reaction on deleveloper console output with new address selector value +export const App = () => ( + <> + + + +); ``` -[Edit on RunKit](https://runkit.com/betula/60338ff8dbe368001a10be8c) +[Play wrapped on CodeSandbox](https://codesandbox.io/s/realar-component-level-scope-classes-m0i10?file=/src/App.tsx) -**cache** +This feature can be useful for removing logic from the body of a component to keep that free of unnecessary code, and therefore cleaner. -`cache` - is the decorator for define `selector` on class getter. +#### React context access visibility level ```javascript -class Todos { - @prop items = []; +const Counter = () => { + const { value, inc } = useScoped(CounterLogic); - @cache get completed() { - return this.items.filter(item => item.completed); - } + return ( +

{value}

+ ); } -``` -**cycle** - -```javascript -const [get, set] = value(0); - -cycle(() => { - console.log(get() + 1); -}); - -set(1); -set(2); - -// In output of developer console will be 1, 2 and 3. +export const App = () => ( + + + + + + + +); ``` -[Edit on RunKit](https://runkit.com/betula/601a733c5bfc4e001a38def8) -- Takes a function as reactive expression. -- After each run: subscribe to all reactive values accessed while running -- Re-run on data changes +[Play wrapped on CodeSandbox](https://codesandbox.io/s/realar-context-component-level-scope-classes-wivjv?file=/src/App.tsx) -**sync** -```javascript -const [getSource, setSource] = value(0); -const [getTarget, setTarget] = value(0); -sync(getSource, setTarget); -// same as sync(() => getSource(), val => setTarget(val)); -setSource(10); -console.log(getTarget()) // 10 -``` -[Edit on RunKit](https://runkit.com/betula/601a73b26adfe70020a0e229) +### API + +- [value](./docs/api.md) +- [selector](./docs/api.md) +- [on](./docs/api.md) +- [cache](./docs/api.md) +- [cycle](./docs/api.md) +- [sync](./docs/api.md) -_Documentation not ready yet for `pool`, `contextual`, `initial`, `mock`, `unmock`, `free`, `transaction`, `untrack`, `isolate`, `un` functions. It's coming soon._ ### Demos @@ -477,6 +290,7 @@ _Documentation not ready yet for `pool`, `contextual`, `initial`, `mock`, `unmoc + [Todos](https://github.com/realar-project/todos) - todomvc implementation. + [Jest](https://github.com/realar-project/jest) - unit test example. + ### Articles + [Multiparadigm state manager for React by ~2 kB.](https://dev.to/betula/multiparadigm-state-manager-for-react-by-2-kb-4kh1) @@ -491,23 +305,9 @@ npm install realar yarn add realar ``` -And update your babel config for support class decorators and using [babel plugin](https://github.com/betula/babel-plugin-realar) for automatic observation arrow function components. +- If you prefer classes with decorators [update your babel config for support](https://babeljs.io/docs/en/babel-plugin-proposal-decorators). +- And configure [babel jsx wrapper](https://github.com/betula/babel-plugin-realar) for automatic observation arrow function components if you want to use it. -```javascript -//.babelrc -{ - "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - ["@babel/plugin-proposal-class-properties", { "loose": true }], - ["realar", { - "include": [ - "src/components/*", - "src/pages/*" - ] - }] - ] -} -``` Enjoy and happy coding! diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..f1e7c1c1 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,132 @@ +### API + +**value** + +The first abstraction of Realar is reactive container - `value`. +The `value` is a place where your store some data as an immutable struct. +When you change value (rewrite to a new immutable struct) all who depend on It will be updated synchronously. + +For create new value we need `value` function from `realar`, and initial value that will store in reactive container. +The call of `value` function returns array of two functions. +- The first is value getter. +- The second one is necessary for save new value to reactive container. + +```javascript +const { get, set } = value(0); + +set(get() + 1); + +console.log(get()); // 1 +``` +[Edit on RunKit](https://runkit.com/betula/6013af7649e8720019c9cf2a) + +In that example +- for a first we created `value` container for number with initial zero; +- After that, we got the value, and set to its value plus one; +- Let's print the result to the developer console, that will is one. + +We learned how to create a value, set, and get it. + +**on** + +The next basic abstraction is expression. +Expression is a function that read reactive boxes or selectors. It can return value and write reactive values inside. + +We can subscribe to change any reactive expression using `on` function _(which also works with signal)_. + +```javascript +const { get, set } = value(0); + +const next = () => get() + 1; + +on(next, (val, prev) => console.log(val, prev)); + +set(5); // We will see 6 and 1 in developer console output, It are new and previous value +``` +[Edit on RunKit](https://runkit.com/betula/6013ea214e0cf9001ac18e71) + +In that example expression is `next` function, because It get value and return that plus one. + +**selector** + +Necessary for making high-cost calculations and cache them for many times of accessing without changing source dependencies. And for downgrade (selection from) your hierarchical store. + +```javascript +const store = value({ + address: { + city: 'NY' + } +}); + +const address = selector(() => store.val.address); + +on(address, ({ city }) => console.log(city)); // Subscribe to address selector + +console.log(address.val.city); // Log current value of address selector + +store.update(state => ({ + ...state, + user: {} +})); +// Store changed but non reaction from address selector + +store.update(state => ({ + ...state, + address: { + city: 'LA' + } +})); +// We can see reaction on deleveloper console output with new address selector value +``` +[Edit on RunKit](https://runkit.com/betula/60338ff8dbe368001a10be8c) + +**cache** + +`cache` - is the decorator for define `selector` on class getter. + +```javascript +class Todos { + @prop items = []; + + @cache get completed() { + return this.items.filter(item => item.completed); + } +} +``` + +**cycle** + +```javascript +const { get, set } = value(0); + +cycle(() => { + console.log(get() + 1); +}); + +set(1); +set(2); + +// In output of developer console will be 1, 2 and 3. +``` +[Edit on RunKit](https://runkit.com/betula/601a733c5bfc4e001a38def8) + +- Takes a function as reactive expression. +- After each run: subscribe to all reactive values accessed while running +- Re-run on data changes + +**sync** + +```javascript +const { getSource, setSource } = value(0); +const { getTarget, setTarget } = value(0); + +sync(getSource, setTarget); +// same as sync(() => getSource(), val => setTarget(val)); + +setSource(10); + +console.log(getTarget()) // 10 +``` +[Edit on RunKit](https://runkit.com/betula/601a73b26adfe70020a0e229) + +_Documentation not ready yet for `pool`, `contextual`, `initial`, `mock`, `unmock`, `free`, `transaction`, `untrack`, `isolate`, `un` functions. It's coming soon._