Open betula opened 3 years ago
Working started: [+] reset [+] promise
Backlog from api-0.6 implementation
[] trigger should be touchable
[] value.touchable(initial) <- The ".from" construction not available for values with "initial" dependency requireds
[] signal.touchable(initial)
[] 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()
Backlog: [] Add callback to join, and combine. After It possible to use object config for join with callback [] .as.trigger [] .as.value.trigger [] .as.signal.trigger
[] signal.trigger.resolved [] value.trigger.resolved
[] test case "should work signal.trigger with configured .pre"
[] signal.trigger.from [] value.trigger.from
[] Add Loader example to documentation
// Second example
const Loader = () => {
const count = value(0);
return {
start: count.updater(v => v + 1),
stop: count.updater(v => v - 1),
pending: count.select(v => v > 0)
}
}
Join brainstorm:
[] Add track|untrack for join with callback
I think better way in that case is removing callback support for current version.
because without callback for combine and join no have any conficts for syntax.
I can use track|untrack for join without callback with no disharmony
--> decision for version 0.6:
[] remove callback support for join and combine
[] Add track|untrack for join
...disharmony was stay
--> decision number two) The second
[] rename join to "attach" or "merge"
Task: The saving consistency of track|untrack for append|attach|join|merge function
Have a reason for join without tracking for a set of rective values.
Have a reason join for signals?
value.combine() -> ok
value.join() -> ok
I cant implement signal.combine([], fn and s.join([], fn) because I have not flag for ready all
signals from argument
signal.combine() -> not ok
signal.join() -> not ok
maybe support combine and join only for values now.
For signal I can use merge
const s = signal(0);
s.merge([() => s.val, ...]).val == 0; without callback
Summary: [] I should implement value.combine([]|{}, fn?), and value(0).combine([] | {}, fn?)
track|untrack for combine? (I can not support untrack for combine values)
[] No have similar for signal in 0.6 versions.
value.from(() => ({ a: a.val, b: b.val })) value.combine({ a, b }) -> better... Always tracked, and it used only for values
value(0).combine - have a problem with track|untrack semantic not for 0.6 version.
I should make only value.combine. Aren't It? Ok. For 0.6 its enough
Backlog [] as.value(dafault_value?) [] support reactionable for untrack [] value().get.untrack()
Backlog [] Add default for resolve "Will" type
const v = value(0).filter().default(0)
Backlog:
const r = signal();
const u = signal<number>();
const v = value(0);
v.reset.by(r);
v.update.by(u, (state, up_value, up_value_prev) => {
return state + (up_value - (up_value_prev || 0));
});
u(10);
r();
And I think to very interesting idea is the convert "reset" function method to signal
const a = value(0);
a.reset.to(() => console.log("reset"));
cosnt v = value(0);
v.flow((value) => {
const ticket = local(() => wait(k));
if (!ticket.val) return flow.stop();
return value;
});
v.flow.filter(() => {
return local(() => wait(k)).val
});
local(expr) // <- bind only first time not necessary bind it every time
// if you need bind every time you should use plain function not local expression.
// synonim of local(expr, []);
local(expr, [<real value dep>, ...])
local(expr, <refresh symbol or expression>);
// or all combination of that
local(expr, <refresh symbol or expression>, [<real value dep>, ...]);
local(expr, [<real value dep>, ...], <refresh symbol or expression>);
const v = value(0);
const s = signal(0);
// wait.all([v, s]) - it is a ready (trigger)
await wait.all([v, s]).promise
await wait.once([v, s]).promise
wait(v) // similar to signal.trigger.from(v) or v.to.signal.trigger()
const k = signal(0);
const m = value(0);
v.flow.wait(m)
.flow.wait.all(k, m) // or synonim to .flow.wait.all([k, m]) -- think about
.flow.wait.race(k, m)
.to((v) => console.log(v));
const k = signal(0);
const m = value(0);
v.flow.wait(m, (v_val, m_val) => return v_val + m_val)
.flow.wait.all([k, m], (v_val, [k_val, m_val]) => return v_val + k_val + m_val) // or stop of course (maybe I can get resolve???)
.flow.wait.race([k, m], (v_val, [k_val, m_val], resolve, v_val_prev) => {});
.to((v) => console.log(v));
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
Think about "chain" abstraction
const a = value(0);
const b = chain(() => a.val);
assert(b.val === undefined);
a(5);
assert(b.val === undefined);
b.input.to(b.output); // "to" better than "watch"
a(6);
assert(b.val === 6);
private finish_purchase = pool(async () => {
if (this.finish_purchase.count > 1) return; // not count.val here
});
const a = pool(async () => {
if (a.count.val > 1) return a.threads[0].promise; // Return promise from first thread
}
assert(a.flat.pending === false);
assert(chan(async () => {}).flat.pending === false);
Collect signals from several values and signals. Worked as last value of set
const a = value(0);
const b = signal(0);
const u = signal.union(a, b);
u.val === 0;
a(5);
u.val === 5;
b(10);
u.val === 10;
Chan - is therm for define async flow. Previous proposals for async are "flow.async", pipe.
const a = chan(async (p, k, m) => {}, empty_val?);
a(1,2,3);
const b = a.chan(async (a_val, prev_a_val) => {
});
chan(async () => {})
.flow.filter()
.flow.filter.untrack(() => this.loaded)
.watch.once(() => {});
chan.untrack(async () => {})
.join(b)
.select(([a,b]) => a + b)
.flow.untrack(() => {}, empty_val?)
.watch(() => {})
chan(async () => {}).wrap.filter.not()
// ...
chan.untrack(async () => {}, empty_val)
value.from(() => {})
.chan(async () => {}, empty_val)
.flow.untrack(() => {}, empty_val?)
.flow((a) => a + b.val)
.watch(() => {});
Chan is not a "pool" because "chan" provides only one value, and flows around that value. Overwise "pool" provides a group of parallel or series async execution processes.
But both these meanings can be used with similar async operators.
const t = value(0)
.view(() => {})
.wrap.throttle(300)
.chan.debounce(300);
// .flow.debounce(300); ??
// const t = pool(async () => {}) // "pool" untracked by default for safety reason
// .wrap.throttle // hmmm multiples of parameters and throttle can be strange combination.
// Necessary to think about "throttle" and "debounce" for "pool".
Possible typecast syntax
chan(async () => {}).select.untrack(() => {});
signal(0).to.value() // vs "signal(0).to.value"
value(0).to.signal()
signal(0).to.ready()
signal(0).promise
value(0).promise
value(0).to.ready().promise // promise recreate on demand // vs value(0).to.ready.promise
value.flag()
value.flag.not()
value.ready()
signal.flag() // equivalent to "value.flag"
signal.ready()
// Think about rename "value.ready" to "value.once" or "value.trigger" ...
signal(0).to.trigger() // The primary candidate for changing to
signal(0).to.once() // hmm..
pool.single(async () => {
})
// or
pool(async () => {
}).single()
// Will be same result
pool.debounce(300).single().flow(async () => {});
// ok
const a = value(0);
const b = value(0);
const c = value(0);
// For a first error on next cases
a.flow.async(async (a) => {
const data = b.flow(() => {}).val; // Error here, necessary for using isolate here
}};
// Flow async proposal
const s = a.flow.async(async (a) => {
if (!a) return stoppable.stop();
return await load_subscriptions();
});
s.val // subscriptions or undefined
s.initialized.val
s.error.val
s.pending()
a.flow.async.debounce(300)
.pipe(
customDebounce,
async (a) => {
if (!a) return stoppable.stop();
const m = c.val; // Subscrible to m or not??
return await load_subscriptions(m);
}
); // readonly async flow
const all_ok = value.combine(a, s.initialized).select(([a, s]) => a && s) // or value.combine([a, s.initialized])
// Rename pool to asyncs)))
// Constructors
value.async(async (v) => {});
signal.async(async (v) => {});
a.flow.async(async (a) => {});
const m = a.flow.async(async (a) => {}).single();
const h = a.sub.async(reactionable, async (state, val) => {}); // h.pending.val
const h_2 = pool(async (a,b,c,d,e) => {
stoppable.stop();
});
// Syntax possibilities
a.async
.debounce(300)
.pipe(
customDebounce,
async (a) => {
if (!a) return stoppable.stop();
const m = c.val; // Subscrible to m or not?? (untrack inside pipe section... hmmmm)
return await load_subscriptions(m);
}
)
.flow(async (a) => {
return await load(a, b.val, c.val); // Depend on change any of that values
})
; // readonly async flow
// Pipe <> Flow same words for not obvious differences... hmm
const s = flow.async(async () => {
return await load(a.val, b.val, c.val);
});
// Syntax possibilities
const t = a.async
.debounce(300)
.flow.untrack(
customDebounce,
async (a) => {
if (!a) return stoppable.stop();
const m = c.val; // not subscribe to c
return await load_subscriptions(m);
}
)
.flow(async (a) => {
return await load(a, b.val, c.val); // subscribe to b and c
})
; // readonly async flow
t.val
t.ready.val
t.error.val
t.pending.val
// flow factory (only track, untrack unsupported)
const s = flow.async(async () => {
return await load(a.val, b.val, c.val);
});
const f_sync = flow(() => {
return sync_load(a.val, b.val, c.val);
});
// And pool
const p = pool(async (a,b,c,d,e) => {
stoppable.stop();
});
p.pending.val
const a = pool(async () => {});
a.single()
a.debounce(300)
a.throttle()
// or
a.pipe.single()
a.pipe.debounce(300)
a.pipe( ... ) // what is It?
and for values and signals
const a = value(0);
a.pool.debounce(100) // what is it?
selector(init_value?, (stop) => {});
selector((stop?) => {});
cycle((stop) => {});
on((stop) => {}, () => {});
// But I think pool no need to use "(stop) =>" syntax, maybe stoppable() is better for pool?
// pool.stop
pool(async () => {
const stop = pool.stop;
pool.stop();
pool.stop.throw();
});
pool.stoppable((stop) => {
return async () => {
}
});
Should be added: signal.trigger.from signal.trigger.flag.from
(Declined) proposal: signal.flag and value.flag
const flag = signal(false).pre((v) => !!v);
const v = value({});
v.proxy.a = 10;
v.update.proxy(p => { // one transaction
p.b = (p.a += 15);
});
const s = signal();
const v = value();
on(s, (s_v) => v.val += s_v);
on(() => [s,v], console.log);
on.transaction(s, (s_v) => v.val += s_v); // used one transaction with changed s
// Think about
const q = queue<Type?>(init?: Type[]);
// q(10); // Think about queue - is a reactive value with set of elements or is the signal for add next one... Hmm.
// q.queue // []
q.front // top
q.back // last element
// q.all // array of elements in queue
q.size
q.clear()
//
q.val
q.first: Value
q.last: Value
q.size: Value
q.active: Value
q.clear()
// Support queue for q.wrap and q.view // think about api
// val: <array_of_elements> readonly
// q(<new_element>)
Or q() - no possible, but
q.add(value: Type)
q.val: Type
q.first: Value
q.last: Value
q.size: Value
q.active: Value
q.clear()
// How I can make queue with limited size same as in my swipe impl?
q = queue();
q(values: Type[]);
q.set(values: Type[]);
q.update(...);
q.val: Type;
q.add(value: Type);
q.first: Value
q.last: Value
q.size: Value
q.active: Value
q.empty: Value
q.clear()
//
const t = q.first
t.val: Type
t.started: Value
t.start();
t.release(); // remove item from queue (or dequeue)
// or
t.val: Type
t.start: ready(false).to(true)
t.remove(); // remove item from queue // or ready(false).to(true) too
Decorating add signal
const q = queue<Type>([], (queue, value: Type) => {
const excluded = queue.slice(3);
// const data = queue..
// How to convert value to queue element?
});
const q = queue<Type>();
q.before (Signal)
q.before((value) => {
stoppable.stop();
transaction
check q
remove unnecessary q elems
add new
finish transaction
});
const a = value(5);
const b = value(1);
const unsub = a.interceptor((new_value, current_value, stop?) => {
if (new_value == 1) {
b.set(10);
stop();
return;
}
return current_value + new_value;
});
// stoppable supported
a(1); // b.val === 10, a.val === 5
a(2); // a.val === 7
Think about syntax for sub
interceptions
const v = value(0);
const s = signal(0);
v.sub.intercept(s, (v_value, new_signal_value, stop ) => {
///
});
v.intercept.sub // alias
// or
v.sub.intercept(s, (v_value, new_signal_value, prev_signal_value ) => {
stoppable.stop();
});
v.intercept.sub // alias
pool.single(async ({ cancelled }) => {
const zip = await search();
if (cancelled()) return;
return await unpack(zip);
});
// If exists active promise each query return It
// pool.throttle(150, async ({ cancelled, abortController?.., cancel }) => {
pool.throttle(150, async ({ stopped, abortController?.., stop }) => {
const zip = await search();
if (zip === 0) stop();
if (stopped()) return;
return await unpack(zip);
});
pool.debounce(150, async function * ({ cancel }) {
const zip = yield search();
if (zip === 0) cancel();
return yield unpack(zip);
});
It is offtopic, but I want to think about throttle and debounce for signals:
const start = signal();
const delayedStart = signal.throttle(150, start);
const a = value();
const delayedA = value.throttle(150, a);
const p = pool(async () => await search());
const delayedP = pool.throttle(150, p);
Maybe only one throttle
function for all necessaries.
const a = value(0);
return (
<p>{a.jsx}</p>
)
return (
<p>{a.jsx(v => v.map((i, k) => <i key={k}>{i}</i>))}</p>
)
:wink:
a.jsx
a.jsx.if
a.jsx.else
a.jsx.ifelse
a.jsx.map
// or
jsx(a)
jsx.if(a)
jsx.map(a)
// etc..
For making super performant interfaces 👍
This abstraction is necessary for the implementation of serial operations. Reactions on each section of the serial operations.
const m = machine()
.step(a, () => {})
.step(b, () => 0)
.step(machine.oneOf()
.select(a, () => {})
.select(b)
.select(c), () => {})
.loop();
But I think the yield generator function is an interesting way of decision.
loop(function *() {
yield a;
yield b;
yield loop.oneOf([a, b, c]) // or loop.oneOf().select(a, () => {}).select(b, () => {})... chain function
yield loop.race([a, b, c])
yield loop.all([a, b, c])
// Promise namespace operations for suggest
});
const v = value(0);
const readonly_v = v.readonly();
Should research about argument passing to "prop" decorator, for the easy binding reactive container to class property:
class A {
@prop(this.store) flat_store
}
It should be extract object or array of reactive containers to object or array of usual javascript values.
const email = value('a@x');
const values = value.extract({ email });
console.log(values.email) // a@x.com
Possible kinds of name: "values" consistent with useValues
input.handler <- (ev) => ev.target.value
// proposal for another names.
// input.delegate
// input.change
// input.event
input.valid
input.valid.pending
input.validator(
signal<string>().map((state) => /a/.test(state))
)
// input.validator.async() // or
// Interested but no consistency to other code base.
// input.validator((signal) => {
// return signal.as.chan().map(async () => {})
// });
// Final perfect version
input.validator([
signal<string>().filter() <-- what will happens if validator will be stopped
chan<string>().map(async (state) => await fetch_is_valid(state)),
(state) => /aa/.test(state),
async (state) => await ext_test(state)
]);
Improve "select" to "select.multiple" signature
const s = select({ a: (state) => state.a });
The argument can be function, reactive container, object map, or array tuple.
const pipe_double = signal<number>().map(state => state * 2);
value(1).map(pipe_double).val // 2
value(1).map([pipe_double, pipe_double]).val // 4
// Use signature as an array or as reactive container, or usual function signature.
https://github.com/reduxjs/reselect/blob/master/typescript_test/test.ts#L479
// typings:expect-error
const baz2: string = baz;
https://codesandbox.io/s/realar-api-unsubscribe-scopes-control-0sziu?file=/src/App.tsx
Warning: Cannot update a component (`fn`) while rendering a different component (`Form`). To locate the bad setState() call inside `Form`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Form (https://0sziu.csb.app/src/App.tsx:30:35)
at fn (https://0sziu.csb.app/node_modules/realar/build/index.js:697:30)
const a = signal<number>();
const b = signal<number>();
sync(a, b);
sync(b, a);
Signals connection without the infinity loop error
I should realize a way to the bi-directional connection between signals.
const a = signal(0);
const b = signal(0);
sync.bi(a, b);
a.val++
b.val++
assert(a.val === 2)
signal
.from(() => {
return [
buyAnnualLocale.val,
sharedPurchases().annual_subscription.val
]
})
.select(([locale, sub]) => { // both variables have one type "string | Subscription | undefined"
if (!sub) return void 0;
const localized_price = (sub as any)?.localizedPrice;
if (!localized_price) return void 0;
return locale.text
})
const y: Value<boolean> = value(false);
/*
Type 'Value<false, false>' is not assignable to type 'Value<boolean, boolean>'.
Type 'Value<false, false>' is not assignable to type '{ (value: boolean): void; set(value: boolean): void; }'.
Types of parameters 'value' and 'value' are incompatible.
Type 'boolean' is not assignable to type 'false'.ts(2322)
*/
const h = cycle(() => {});
h.stop();
h.run();
sharedEye().enabled
.as.signal()
.filter()
.filter(isNext.map(flag => !flag)) // TODO: shortcut not isNext.not()
export const swipeAnimDirection = value<undefined | 'up' | 'down'>();
value<void | number>()
.as.signal()
.filter() // Should remove undefined | null | void from Will signal type
.to(onlyNumberSignal);
const MyLogic = (initial: string) => {}
const MyScope = scope(MyLogic)
const A = () => {
const B = useJsx(() => {
const logic = useScoped(MyLogic)
/* proposal: use scope logic from local section
useLocal(() => {
const logic = scoped(MyLogic) // Warning scope recreation
const logic = scoped.current(MuLogic) // Think about
});
*/
return
});
return <MyScope initial={'hello'}><B>{children}</B></MyScope>
}
loader_queue.sync(async queue => {
// should be untracked here
})
const toggle = value(false).pre((_, state) => !state);
const enabled = toggle.select(); // Incorrect: ValueReadonly<unknown>
const t = value.will<number>();
expect(t.val).toBeUndefined();
t.map(n: number => n + 1);
const takeUser = value();
const takeAuth = value();
takeAuth.onChange(takeUser.andUpdate)
takeUser.andSet()
takeUser() - alias for takeUser.andGet
const start = signal();
start.get - doesn't allowed
start.fire(new_value)
takeUser.onChange(start.fire)
start.getValue() - returns value instance
isDirty, isUndefined, isTouched
All methods created only by demand.
start.flow
start.pre
takeUser.compose
"ready" -> "trigger" "sub" -> "update.by"