Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
canvas
/
node_modules
/
@instructure
/
updown
/
Filename :
README.md
back
Copy
# updown Apply a set of inter-dependent functions, and later undo their effects. This routine is suitable when you need to run separate pieces of logic that are order-dependent: the dependencies are explicit and made clear to the reader, and the order in which they are declared does not govern the order in which they are run. ```javascript import up from 'updown' await up({ up: (a) => { /* do something when everything is ready */ console.log(a) // => "hi" }, requires: [ { up: () => { /* but cause some side-effects first... */ window.im_bad = 1 return { down: () => { /* ...and clean them up when asked */ delete window.im_bad } } } }, { /* inject dependencies... */ up: () => Promise.resolve({ value: 'hi' }), requires: [ { /* ...but do even more things first... */ up: ..., requires: [...] } ] } ] }) // => { down: Function, value: undefined } ``` The functions can be asynchronous or not, the interface remains identical. See also the `example.ts` file in this repository for sample usage that is written in a somewhat more real-world way. ## Usage and going up The input to the `up` function is a tree of objects - its nodes - that contain two significant properties: (1) "up", which points to the function to run, and (2) "requires", which is the list of other nodes that must be run _before_ it. ```javascript const A = { up: () => {}, requires: [] } const B = { up: () => { ... }, requires: [A] } ``` A node is brought "up" exactly once, and the time that happens is when all of its dependencies have been satisfied. In the example above, `A` will be brought up before `B`. You don't need to worry about the order in which dependencies are declared; `up` will figure that out. A node is considered to be up when its `up` function returns. However, if it returns a Promise, then `updown` will wait on that Promise to be settled before considering it to be up. Once a node is determined to be up, then other nodes which depend upon it will be brought up in turn. With that said, the tree is expected to be free of circular dependencies. We don't attempt to detect such cases and the behavior of `up` is undefined if this constraint is not held. ## Producing values A node may produce a value to be used by other nodes which depend upon it. The value provided is opaque to and is not examined by the updown logic. `up` may return `void` (or a Promise that resolves to `void`), in which case the value is `undefined` and the `down` action is nothing. `up` may also return either an Object (or a Promise which resolves to an Object); that Object must match the type signature: ```javascript { value?: unknown, down?: () => (void | Promise<void>) } ``` It is the `value` property that is considered the value produced by the `up` function and which is passed as an argument to dependents, discussed in the next section. ### Considerations for positional arguments The value itself is provided as an argument to the dependent's `up` function at the position equal to where the dependency was declared in the `requires` set. For example, the node `B` will receive the value of `A` as the second argument: ```javascript const A = { up: () => ({ value: 5 }), requires: [] } const B = { up: (_, valueOfA) => {}, requires: [C, A] } │ │ │ └─ position of dependency └─ position of dependency value ``` This symmetry suggests a particular arrangement for the dependencies we declare in `requires` whereby ones that produce values are placed _before_ others that don't. Consider a different example where nodes `A` and `B` do produce values whereas `C` does not: ```javascript const A = { up: () => ({ value: 1 }), requires: [] } const B = { up: (a) => ({ value: a + 1 }), requires: [A] } const C = { up: () => (), requires: [A,B] } up({ up: (c, a, b) => { console.log(c, a, b) /* => undefined, 1, 2 */ }, requires: [C, A, B] }) ``` As you see in the body of `up` for the last node, `C`'s value was undefined and looked awkward. Had we declared it _after_ `A` and `B`, its argument could then be omitted entirely, which would make for a more natural interface: ```javascript up({ up: (a, b) => { console.log(a, b) /* => 1, 2 */ }, requires: [A, B, C] }) ``` In practice, the functions whose output you care about are declared earlier in `requires` so that you may easily reference them. Remember that the order in which you declare the dependencies does not affect the order in which they are run, so you are safe to arrange them in a way that suits you. ### Considerations for Capabilities that are not idempotent Suppose we have two entirely separate trees of dependencies: const A1: Capability = { up: ..., requires: [B, C, E] }; const A2: Capability = { up: ..., requires: [C, D, E] }; But C's up action is not idempotent so running it twice might result in undefined behavior, so it can be protected with `oncePerPage` meaning it will only execute once per global code execution. `oncePerPage` can be imported by name from the `updown` package if needed. E is also a dependency of both trees, but pretend that its up action is idempotent, so it won't matter if we run it more than once. Thus it will run twice in this case if not wrapped in `oncePerPage`. Note that when possible, it's easier (and preferred) to simply define a new dependency A with `requires: [ A1, A2 ]` and then `updown` itself would take care of assuring that C only executed once. But sometimes disparate sections of code running in the same environment need to run completely different instances of `up` and all the respective sets of descendent requirements; whenever this is the case one must be careful to use `oncePerPage` to guard shared capabilities from bringing themselves up more than once if that would create problems. The included `example.ts` demonstrates how to include `oncePerPage` and how to use it to protect capability `up` functions from executing more than once. ### Considerations for values that are unresolved Promises Be careful: If a node returns an object with a value which is an unresolved Promise, `updown` will _not_ wait on it to be settled before considering your node to be up! If you mean to block your dependents until a Promise settles, be sure to return that Promise as the result of `up` itself. For example, the following will cause `updown` to proceed immediately after calling this `up` function: ```javascript up({ up: () => ({ value: new Promise(resolve => resolve(...)); }) }); // WILL NOT WAIT! ``` Whereas this will cause it to wait until the Promise settles: ```javascript up({ up: () => new Promise(resolve => resolve({ value: ... })) }); // This will wait ``` The usual use case is for `up` itself to return the Promise, which allows `updown` to automatically manage the asynchronous running of dependents at the proper times. Since the `value` inside the return object is opaque to `updown` an implementation is certainly free to set it to anything appropriate for that code's logic, including an unresolved Promise; just be aware that that alone will not block bringing up dependent nodes. ## Going down A node that causes side-effects while going up should implement a counterpart function that restores those effects by defining a function at the "down" property of its output. ```javascript { up: () => { window.be_bad = 1 return { down: () => { delete window.be_bad } } }, requires: [] } ``` Nodes are brought down in a LIFO fashion. ## Error handling Should any node fail to go up - either by throwing an Error or by producing a rejected Promise - the chain is aborted. The error is decorated with a custom property "down" that can be used to tear down the nodes that were successfully brought up to that point. ```javascript try { await up(...) } catch (e) { // do something with error: ... // clean up: await e.down() } ``` Errors caught while going down are tracked and provided to you but they do not stop other nodes from going down. In this case, `down` rejects with the last error encountered. ## Input integrity The `up` routine makes no attempts at validating the structure of the nodes you provide at runtime; it expects them to be valid. To interface properly with this package, please make sure you're utilizing the available TypeScript types and run your own integration through the `tsc` checker at build-time.