More dataverse docs

This commit is contained in:
Aria Minaei 2023-01-17 17:58:33 +01:00
parent 1e1a5f5831
commit bab95ddad9

View file

@ -7,6 +7,13 @@ describe(`Dataverse guide`, () => {
// Since this is a test suite, you should be able to run it in [debug mode](https://jestjs.io/docs/en/troubleshooting) // Since this is a test suite, you should be able to run it in [debug mode](https://jestjs.io/docs/en/troubleshooting)
// and inspect the value of variables at any point in the test. // and inspect the value of variables at any point in the test.
describe(`Chapter 0 - Concepts`, () => {
// There 4 main concepts in dataverse:
// - Atoms, hold the state of your application.
// - Pointers are a type-safe way to get/set/react-to changes in Atoms.
// - Prisms are functions that react to changes in atoms and other prisms.
// - Tickers are a way to schedule and synchronise computations.
// before we dive into the concepts, let me show you how a simple dataverse setup looks like. // before we dive into the concepts, let me show you how a simple dataverse setup looks like.
test('A simple dataverse setup', () => { test('A simple dataverse setup', () => {
// In this setup, we're gonna write a program that renders an image of a sunset, // In this setup, we're gonna write a program that renders an image of a sunset,
@ -77,7 +84,7 @@ describe(`Dataverse guide`, () => {
// our listener would be called again. // our listener would be called again.
expect(listener).toBeCalledTimes(2) expect(listener).toBeCalledTimes(2)
// and that would be it for our simple program. But let's take stock of the concepts we've encountered so far. // And that would be it for our simple program. But let's take stock of the concepts we've encountered so far.
// 1. We've created an `Atom` to hold the state of our program. // 1. We've created an `Atom` to hold the state of our program.
// 2. We've created a `prism` to create a reactive function out of `timeOfDay`. // 2. We've created a `prism` to create a reactive function out of `timeOfDay`.
// 3. We've used a pointer to get the value of `timeOfDay` from the state. // 3. We've used a pointer to get the value of `timeOfDay` from the state.
@ -87,41 +94,42 @@ describe(`Dataverse guide`, () => {
// But let's wrap this test case up by cleaning up our resources. // But let's wrap this test case up by cleaning up our resources.
unsubscribe() unsubscribe()
}) })
})
// prisms are a way to create a value that depends on other values. describe(`Chapter 1 - What is a prism?`, () => {
// prisms can be hot or cold, they have dependencies and dependents, and hot prisms can be stale or fresh. // A Prism is a way to create a value that depends on other values.
// let's start with a simple example: // let's start with a simple example:
test(`using a pretty useless prism`, async () => { test(`A pretty useless prism`, async () => {
// each prism has a calculate function that it runs to calculate its value. let's make a simple function that just returns 1 // Each prism has a calculate function that it runs to calculate its value. let's make a simple function that just returns 1
const calculate = jest.fn(() => 1) const calculate = jest.fn(() => 1)
// now we can make a prism out of it // Now we can make a prism out of it
const pr = prism(calculate) const pr = prism(calculate)
// now, this prism is pretty useless. it doesn't depend on anything, and it doesn't have any dependents. // Now, this prism is pretty useless. It doesn't depend on anything, and it doesn't have any dependents.
// but we can already illustrate some of the concepts that are important to understand prisms. // But we can already illustrate some of the concepts that are important to understand prisms.
// `calculate` won't be called until it's needed // Our `calculate` function will never be called until it's actually needed - prisms are lazy.
expect(calculate).not.toHaveBeenCalled() expect(calculate).not.toHaveBeenCalled()
// we can get the value of the prism, which will be what `calculate` returned // We can get the value of the prism, which will be what the `calculate` function returned,
expect(pr.getValue()).toBe(1) expect(pr.getValue()).toBe(1)
// now the calculate function will have be called // and of course our calculate function will have been called.
expect(calculate).toHaveBeenCalledTimes(1) expect(calculate).toHaveBeenCalledTimes(1)
// now, you might expect that if we call `getValue()` again, the calculate function won't be called again. // Now, you might expect that if we call `getValue()` again, the calculate function won't be called again.
// but that's not the case. the calculate function will be called again, because the prism is cold. // But that's _not_ the case. the calculate function will be called again, because our prism is cold.
// we'll talk about cold/hot in a bit, but let's just say that cold prisms are calculated every time they're read. // We'll talk about cold/hot in a bit, but let's just say that cold prisms are calculated every time they're read.
pr.getValue() pr.getValue()
expect(calculate).toHaveBeenCalledTimes(2) expect(calculate).toHaveBeenCalledTimes(2)
// we can even check whether a prism is hot or cold. Ours is cold. // We can even check whether a prism is hot or cold. Ours is cold.
expect(pr.isHot).toBe(false) expect(pr.isHot).toBe(false)
// we'll get to hot prisms soon, but let's talk about dependencies and dependents first. // We'll get to hot prisms soon, but let's talk about dependencies and dependents first.
}) })
// prisms can depend on other prisms. let's make a prism that depends on another prism. // prisms can depend on other prisms. let's make a prism that depends on another prism.
@ -132,72 +140,72 @@ describe(`Dataverse guide`, () => {
const calculateATimesTwo = jest.fn(() => a.getValue() * 2) const calculateATimesTwo = jest.fn(() => a.getValue() * 2)
const aTimesTwo = prism(calculateATimesTwo) const aTimesTwo = prism(calculateATimesTwo)
// clear the count of mocks // let's define a function that clears the count of mocks, as we're gonna do that quite a few times.
function clearMocks() { function clearMocks() {
calculateA.mockClear() calculateA.mockClear()
calculateATimesTwo.mockClear() calculateATimesTwo.mockClear()
} }
// now, `aTimesTwo` depends on `a`. // So, `aTimesTwo` depends on `a`.
// In our parlance, `aTimesTwo` is a dependent of `a`, and `a` is a dependency of `aTimesTwo`. // In our parlance, `aTimesTwo` is a dependent of `a`, and `a` is a dependency of `aTimesTwo`.
// now if we read the value of `aTimesTwo`, it will call `calculateATimesTwo`, which will call `calculateA`: // Now if we read the value of `aTimesTwo`, it will call `calculateATimesTwo`, which will call `calculateA`:
expect(aTimesTwo.getValue()).toBe(2) expect(aTimesTwo.getValue()).toBe(2)
expect(calculateA).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1)
expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateATimesTwo).toHaveBeenCalledTimes(1)
clearMocks() clearMocks()
// and like we saw in the previous test, if we read the value of `aTimesTwo` again, it will call both of our calculate functions again: // And like we saw in the previous test, if we read the value of `aTimesTwo` again, it will call both of our calculate functions again:
aTimesTwo.getValue() aTimesTwo.getValue()
expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateATimesTwo).toHaveBeenCalledTimes(1)
expect(calculateA).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1)
clearMocks() clearMocks()
// but if we read the value of `a`, it won't call `calculateATimesTwo`: // But if we read the value of `a`, it won't call `calculateATimesTwo`:
a.getValue() a.getValue()
expect(calculateATimesTwo).toHaveBeenCalledTimes(0) expect(calculateATimesTwo).toHaveBeenCalledTimes(0)
expect(calculateA).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1)
clearMocks() clearMocks()
// now let's see what happens if we make our prism hot. // Now let's see what happens if we make our prism hot.
// one way to do this, is to add an `onStale` listener to our prism. // One way to make a prism hot, is to add an `onStale` listener to it.
const onStale = jest.fn() const onStale = jest.fn()
const unsubscribe = aTimesTwo.onStale(onStale) const unsubscribe = aTimesTwo.onStale(onStale)
// as soon as we do this, the prism will become hot. // As soon as a prism has an `onStale` listener, it becomes hot...
expect(aTimesTwo.isHot).toBe(true) expect(aTimesTwo.isHot).toBe(true)
// and so will its dependencies: // ... and so will its dependencies, and _their_ dependencies, and so on.
expect(a.isHot).toBe(true) expect(a.isHot).toBe(true)
// so let's see what happens when we read the value of `aTimesTwo` again: // So, let's see what happens when we read the value of `aTimesTwo` again:
aTimesTwo.getValue() aTimesTwo.getValue()
// `calculateATimesTwo` will be called again, // Since this is the first time we're calculating `aTimesTwo` after it went hot, `calculateATimesTwo` will be called again,
expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateATimesTwo).toHaveBeenCalledTimes(1)
// and so will `calculateA`, // and so will `calculateA`,
expect(calculateA).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1)
clearMocks() clearMocks()
// but if we read `aTimesTwo` again, none of the calculate functions will be called again. // But if we read `aTimesTwo` again, none of the calculate functions will be called again.
aTimesTwo.getValue() aTimesTwo.getValue()
expect(calculateATimesTwo).toHaveBeenCalledTimes(0) expect(calculateATimesTwo).toHaveBeenCalledTimes(0)
expect(calculateA).toHaveBeenCalledTimes(0) expect(calculateA).toHaveBeenCalledTimes(0)
clearMocks() clearMocks()
// this behavior propogates up the dependency chain, so if we read `a` again, `calculateA` won't be called again, // This behavior propogates up the dependency chain, so if we read `a` again, `calculateA` won't be called again,
// because its value is already fresh. // because its value is already fresh.
a.getValue() a.getValue()
expect(calculateA).toHaveBeenCalledTimes(0) expect(calculateA).toHaveBeenCalledTimes(0)
clearMocks() clearMocks()
// at this point, since none of our prisms depend on a prism whose value will change, they'll remain // At this point, since none of our prisms depend on a prism whose value will change, they'll remain
// fresh forever. // fresh forever.
a.getValue() a.getValue()
aTimesTwo.getValue() aTimesTwo.getValue()
@ -209,7 +217,7 @@ describe(`Dataverse guide`, () => {
clearMocks() clearMocks()
// but as soon as we unsubscribe from our `onStale()` listener, the prisms will become cold again. // But as soon as we unsubscribe from our `onStale()` listener, the prisms will become cold again,
unsubscribe() unsubscribe()
expect(aTimesTwo.isHot).toBe(false) expect(aTimesTwo.isHot).toBe(false)
expect(a.isHot).toBe(false) expect(a.isHot).toBe(false)
@ -227,31 +235,31 @@ describe(`Dataverse guide`, () => {
clearMocks() clearMocks()
// now, one more thing before we move on. What will if we make `a` hot, but not `aTimesTwo`? // Now, one more thing before we move on. What will happen if we make `a` hot, but not `aTimesTwo`?
// let's try it out. // Let's try it out.
const unsubcribeFromAOnStale = a.onStale(() => {}) const unsubcribeFromAOnStale = a.onStale(() => {})
// a will go hot // `a` will go hot:
expect(a.isHot).toBe(true) expect(a.isHot).toBe(true)
// but aTimesTwo will stay cold // but `aTimesTwo` stays cold:
expect(aTimesTwo.isHot).toBe(false) expect(aTimesTwo.isHot).toBe(false)
// now let's read the value of `a` // Now let's read the value of `a`
a.getValue() a.getValue()
// `calculateA` will be called // `calculateA` will be called
expect(calculateA).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1)
// and obviously `calculateATimesTwo` won't be called // And `calculateATimesTwo` won't.
expect(calculateATimesTwo).toHaveBeenCalledTimes(0) expect(calculateATimesTwo).toHaveBeenCalledTimes(0)
clearMocks() clearMocks()
// and if we re-read the value of `a`, `calculateA` won't be called again, becuase `a` is hot and its value is fresh. // And if we re-read the value of `a`, `calculateA` won't be called again, becuase `a` is hot and its value is fresh.
a.getValue() a.getValue()
expect(calculateA).toHaveBeenCalledTimes(0) expect(calculateA).toHaveBeenCalledTimes(0)
clearMocks() clearMocks()
// but if we read the value of `aTimesTwo`, `calculateATimesTwo` will be called, because `aTimesTwo` is cold. // But if we read the value of `aTimesTwo`, `calculateATimesTwo` will be called, because `aTimesTwo` is cold.
aTimesTwo.getValue() aTimesTwo.getValue()
expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateATimesTwo).toHaveBeenCalledTimes(1)
// yet `calculateA` won't be called, because `a` is hot and its value is fresh. // yet `calculateA` won't be called, because `a` is hot and its value is fresh.
@ -268,10 +276,11 @@ describe(`Dataverse guide`, () => {
unsubcribeFromAOnStale() unsubcribeFromAOnStale()
}) })
test('What about state?', () => {
// so far, our prisms have not depended on any changing values, so in turn, _their_ values have never changed either. // so far, our prisms have not depended on any changing values, so in turn, _their_ values have never changed either.
// but what if we want to create a prism that depends on a changing value? // but what if we want to create a prism that depends on a changing value?
// we call those values "sources", and we can create them using the `prism.source()` hook: // we call those values "sources", and we can create them using the `prism.source()` hook:
test('prism.source()', () => {
// let's say we want to create a prism that depends on this value: // let's say we want to create a prism that depends on this value:
let value = 0 let value = 0
@ -408,5 +417,13 @@ describe(`Dataverse guide`, () => {
// in practice, we'll almost never need to use the `source` hook directly, // in practice, we'll almost never need to use the `source` hook directly,
// and we'll never need to provide our own `listen` and `get` functions. // and we'll never need to provide our own `listen` and `get` functions.
// instead, we'll use `Atom`s, which are sources that are already implemented for us. // instead, we'll use `Atom`s, which are sources that are already implemented for us.
})
describe(`Chapter 2 - Atoms`, () => {
// In the final test of the previous chapter, we learned how to create our own sources of state,
// and make a prism depend on them, using the `prism.source()` hook. In this chapter, we'll learn
// how to use the `Atom` class, which is a source of state that's already implemented for us and comes
// with a lot of useful features.
test(`Using Atoms`, () => {}) test(`Using Atoms`, () => {})
})
}) })