sodiumjs
Version:
A Functional Reactive Programming (FRP) library for JavaScript
118 lines (113 loc) • 4.91 kB
text/typescript
import { Stream, StreamWithSend } from "./Stream";
import { Cell } from "./Cell";
import { Transaction } from "./Transaction";
import { Unit } from "./Unit";
import { Source, Vertex } from "./Vertex";
export class Operational {
/**
* A stream that gives the updates/steps for a {@link Cell}.
* <P>
* This is an OPERATIONAL primitive, which is not part of the main Sodium
* API. It breaks the property of non-detectability of cell steps/updates.
* The rule with this primitive is that you should only use it in functions
* that do not allow the caller to detect the cell updates.
*/
static updates<A>(c : Cell<A>) : Stream<A> {
/* Don't think this is needed
const out = new StreamWithSend<A>(null);
out.setVertex__(new Vertex("updates", 0, [
new Source(
c.getStream__().getVertex__(),
() => {
return c.getStream__().listen_(out.getVertex__(), (a : A) => {
out.send_(a);
}, false);
}
),
new Source(
c.getVertex__(),
() => {
return () => { };
}
)
]
));
return out;
*/
return c.getStream__();
}
/**
* A stream that is guaranteed to fire once in the transaction where value() is invoked, giving
* the current value of the cell, and thereafter behaves like {@link updates(Cell)},
* firing for each update/step of the cell's value.
* <P>
* This is an OPERATIONAL primitive, which is not part of the main Sodium
* API. It breaks the property of non-detectability of cell steps/updates.
* The rule with this primitive is that you should only use it in functions
* that do not allow the caller to detect the cell updates.
*/
static value<A>(c : Cell<A>) : Stream<A> {
return Transaction.run(() => {
const sSpark = new StreamWithSend<Unit>();
Transaction.currentTransaction.prioritized(sSpark.getVertex__(), () => {
sSpark.send_(Unit.UNIT);
});
const sInitial = sSpark.snapshot1(c);
return Operational.updates(c).orElse(sInitial);
});
}
/**
* Push each event onto a new transaction guaranteed to come before the next externally
* initiated transaction. Same as {@link split(Stream)} but it works on a single value.
*/
static defer<A>(s : Stream<A>) : Stream<A> {
return Operational.split<A>(s.map((a : A) => {
return [a];
}));
}
/**
* Push each event in the list onto a newly created transaction guaranteed
* to come before the next externally initiated transaction. Note that the semantics
* are such that two different invocations of split() can put events into the same
* new transaction, so the resulting stream's events could be simultaneous with
* events output by split() or {@link defer(Stream)} invoked elsewhere in the code.
*/
static split<A>(s : Stream<Array<A>>) : Stream<A> {
const out = new StreamWithSend<A>(null);
out.setVertex__(new Vertex("split", 0, [
new Source(
s.getVertex__(),
() => {
out.getVertex__().childrn.push(s.getVertex__());
let cleanups: (()=>void)[] = [];
cleanups.push(
s.listen_(Vertex.NULL, (as : Array<A>) => {
for (let i = 0; i < as.length; i++) {
Transaction.currentTransaction.post(i, () => {
Transaction.run(() => {
out.send_(as[i]);
});
});
}
}, false)
);
cleanups.push(() => {
let chs = out.getVertex__().childrn;
for (let i = chs.length-1; i >= 0; --i) {
if (chs[i] == s.getVertex__()) {
chs.splice(i, 1);
break;
}
}
});
return () => {
cleanups.forEach(cleanup => cleanup());
cleanups.splice(0, cleanups.length);
}
}
)
]
));
return out;
}
}