mangojuice-core

1.2.11 • Public • Published

MangoJuice Core

Reference

Table of Contents

Basic

The most common APIs that you will work with most of the time

Block

Block is a top level component in MangoJuice which consists of three parts:

  • Model factory function createModel which should return a plain object. The main requirement for this function is that it should be able to return some model with zero arguments. But it can also accept some arguments as well.
  • A logic class which implements LogicBase interface
  • A View function which is aimed to render the model created by createModel

The View part is optional and it could have different type depending on selected View library to render a model and particular binding library.

Properties

  • Logic LogicBase A logic class that should be attached to the model.
  • createModel Function A function that shuld return a plain object which will be an init model of the block.
  • View any? Something that some particular view library can render

Examples

// You can define block in short way using an object
const MyBlock = {
  createModel: () => ({ field: 1 }),
  Logic: class MyLogic {
    \@cmd SomeCommand() {
      return { field: this.model.field + 1 };
    }
  },
  View: ({ model }) => (
    <div>{model.field}</div>
  )
};
 
run(MyBlock);
// Or you can split it with modules (recommended)
// ./MyBlock/index.js
import Logic from './Logic';
import View from './View';
import { createModel } from './Model';
export default { View, Logic, createModel };
export { View, Logic, createModel };
export type { Model } from './Model';
 
// ./MyBlock/Logic.js
import type { Model } from './Model';
import { LogicBase, cmd } from 'mangojuice-core';
 
export default class MyLogic extends LogicBase<Model> {
  \@cmd SomeCommand() {
    return { field: this.model.field + 1 };
  }
}
 
// ./MyBlock/Model.js
export type Model = { fieldnumber };
export const createModel = () => ({
  field: 1
});
 
// ./MyBlock/View.js
const MyBlockView = ({ model }) => (
  <div>{model.field}</div>
);
export default MyBlockView;
 
// ./index.js
import MyBlock from './MyBlock';
import { run } from 'mangojuice-core';
 
run(MyBlock);

LogicBase

Basic class for every logic. It is not necessaty to inherit an actual logic class from it, but for better type-checking it could be really usefull.

Logic class defines:

The logic class defines all the business-logic of some part of your application. The main goal of the logic – change associated model. Check cmd and Process#exec to have a better understanding how command can be defined and how it is executed.

Properties

  • model Object A model object which is associated with the logic class in parent logic.
  • meta Object An object defined in LogicBase#config method for storing some internal logic state.
  • shared Object An object with some shared state, defined while run process of the app.

Examples

// ./blocks/MyBlock/Logic.js
// @flow
import type { Model } from './Model';
import ChildBlock from '../ChildBlock';
import { Command, cmd } from 'mangojuice-core';
 
export default class MyLogic extends LogicBase<Model> {
  children() {
    return { child: ChildBlock.Logic };
  }
  hubAfter(cmd: Command) {
    if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
      return this.TestCommand(123);
    }
  }
  \@cmd TestCommand(arg) {
    return { field: arg + this.model.field };
  }
}
config

A function that defines init commands and meta object of the logic. Can accept any type and number of arguments. Config will take arguments passed to child function (for example child(MyLogic, 1, 2, 3) defines that the config method of MyLogic will be invoked like config(1, 2, 3))

Could return an object with the fields:

  • initCommands which could be an array or a single Command, that will be executed every time when the logic instance created and associated with some model (when Process#run invoked)
  • meta which could be an object with some internal state of the logic

Examples

config(props) {
  return {
    initCommands: [ this.StartThis, this.SendThat(123) ],
    meta: { something: props.amount }
  };
}
children

This function defines what logic class should be associated with which model field. Should return an object where a key is a model field name, and a value is a logic class or an object that defines logic with arguments (return from child function)

Examples

children() {
  return {
    searchForm: child(SearchForm.Logic, { timeout: 100 }),
    searchResults: SearchResults.Logic
  };
}

Returns Object

computed

Should return an object which defines computed fields of the model. A key of the object is a model field, and value is a function or computed function definition object (return from depends function).

Computed field is lazy, which means that it is computed on the first use of the field and the computation result cached until the next update of the model.

If you want to define a computed field which ueses in computation not only fields from the own model, but maybe some values from child model, or from shared model, then you will need to use depends function to define what models used while computation to observe changes of these models to trigger an update event of the own model (to update views).

Examples

computed() {
  return {
    simple: () => this.model.a + this.model.b,
    withDeps: depends(this.model.child, this.shared).compute(() => {
      return this.model.child.a + this.shared.b + this.model.c;
    })
  };
}

Returns Object

port

A function to handle global event, such as browser-level events, websocket, intervals. The main porpouse of the port is to subscribe to some global events, execute appropriate commands from the logic on the events and remove the handlers when the logic was destroyed.

Parameters

  • exec Function It is a function that execute a passed command
  • destroyed Promise A promise that will be resolved when the logic destroyed

Examples

port(exec, destroyed) {
  const timer = setInterval(() => {
    exec(this.SecondPassed);
  }, 1000);
  destroyed.then(() => clearInterval(timer));
}
hubAfter

Hub for all commands executed in the children blocks. The only arguments is a command object that was executed. Could return a Command or an array of Commands that should be executed next. Most of the time this function will look like a switch, but maybe with some additional rules for the cases. Take a look to Command#is function to have a better understanding how you can determine that the command is exactly what you are waiting for.

Parameters

  • cmd Command Command that was executed in some child logic or in any child of the child and so on down to the leaf of the tree.

Examples

hubAfter(cmd) {
  if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
    return this.HandlerCommand(123);
  }
  if (cmd.is('AnotherChild.SomeAnothercommand')) {
    return [ this.HandlerCommand(321), this.AnotherHandler ];
  }
}

Returns (Command? | Array<Command>?)

hubBefore

Same as LogicBase#hubAfter, but catch commands before it will be actually executed, so you can compare prev and next model state for example.

Parameters

Returns (Command? | Array<Command>?)

cmd

Decorator that converts a logic method to a command factory. The result function (command factory function) is a function that return a Command instnace with the decorated function and arguments that you will pass to a command factory function.

You can use this decorator without arguments or with an options argument (object)

Check Process#exec to see what the origin function could return to do something. If in short, it can return: nothing, command (or factory or array of commands), task, model update object.

Parameters

  • obj Object If only this argument passed then it will be considered as options object which can customize command behaviour.
    • obj.debounce number If greeter than zero then an exeuction of the command will be debounced by given amount of milliseconds
    • obj.throttle number If greeter than zero then an exeuction of the command will be throttled by given amount of milliseconds
    • obj.noInitCall bool If true then debounced/throttled execution won't start with init command call. By default debounced/throttled command will be instantly exected and only then wait for a chance to execute again. With noInitCall=true there is no instant init command call.
    • obj.internal bool Make the command invisible for parent LogicBase#hubBefore and LogicBase#hubAfter. Use it for commands that shouldn't be handled by hubs to increse performance.
    • obj.name string By default name of the command taken from origin function. With this option you can override the name with any other value.
  • methodName
  • descr

Examples

class RegularLogic {
  \@cmd RegularCommand() {
    // do something
  }
}
class RegularLogic {
  \@cmd({ debounce: 300 })
  DebouncedCommand() {
    // if you will call this command 3 times every
    // 100ms then it will be executed 2 times,
    // first right when you call it for a first time,
    // and second in 600ms.
  }
}
class RegularLogic {
  \@cmd({ throttle: 300 })
  ThrottledCommand() {
    // if you will call this command 3 times every
    // 100ms then it will be executed 2 times,
    // first right when you call it for a first time,
    // and second in 300ms.
  }
}
class RegularLogic {
  \@cmd({ throttle: 300, noInitCall: true })
  ThrottledCommand() {
    // if you will call this command 3 times every
    // 100ms then it will be executed 1 time in 300ms.
  }
}
class RegularLogic {
  \@cmd({ internal: true })
  _InternalCommand() {
    // this command can't be caught in `hubBefore` or `hubAfter`
    // in parent logics. Use it carefully.
  }
}

Returns (Function | object) Command factory function (if all three arguemnts passed) or a decorator function with binded options object (that you pass as a first argument)

run

Run given block. It is a short-hand function for running bind and then running a Process by Process#run. Also it returns an additional finished Promise which is resolved when all async tasks finished and all handler commands executed.

Parameters

  • block Block A block that you wan to run
  • opts Object Same options as in bind (optional, default {})

Returns {proc: Process, model: Object, block: Block, finished: Promise} An object which is almost the same as returned from bind, but with additional finished field, which is a Promise that will be resolved when all blocks will execute all commands and all async tasks will be finished.

mount

Mount running block. As third and next arguments you can pass other running blocks which will be stopped with the main running block (useful for HMR).

Parameters

Examples

const MyBlock = {
  createModel: () => ({ field: 1 }),
  Logic: class MyLogic {
    \@cmd SomeCommand() {
      return { field: this.model.field + 1 };
    }
  },
  View: ({ model }) => (
    <div>{model.field}</div>
  )
};
 
mount(new ReactMounter(), run(MyBlock));

Returns {view: any, stop: Function} An object with the result form Mounter#mount function (in view field) and stop function which destroy the process and unmount a view using Mounter#unmount

task

Creates a TaskMeta object that could be returned from async task command. It describes the task that should be executed.

For more information what the Task is and how it should be implemented take a look to TaskMeta.

Parameters

  • taskFn Function A function that could return a Promise

Properties

  • CANCEL string A symbol that you can use to make a promise "cancellable"

Returns Object

cancel

This function should be used to cancel async task execution. Also it can cancel the debounced/throttled command.

  • In case with task, if task was executing while you call a cancel command, the task will be cancelled (execution stopped). Otherwise nothing will happen.
  • In case with debounced/throttled command, if the command scheduled to be executed then the schedule timer will be cleared and the command won't be executed. If the command is not scheduled then nothing happens.

To use it just pass a command or command factory as a first argument and return it from some other command.

Parameters

  • cmd (Command | function) Command or command factory that you wan to cancel. Could be a command that returns task or debounced/throttled command

Examples

class SomeLogic {
  \@cmd CancelSomethingCommand() {
    return [
      cancel(this.TaskCommand),
      cancel(this.DebounceCommand)
    ];
  }
  \@cmd TaskCommand() {
    return task(Tasks.SomeTask);
  }
  \@cmd({ debounce: 300 })
  DebounceCommand() {
    // do something
  }
}

Returns Command Returns a new command that will cancel the execution of the command passed as an argument.

child

Creates an object which describes child logic which will be attached to some model field. Should be used in LogicBase#children to define a logic that should be instantiated with some arguments passed to LogicBase#config.

Parameters

Examples

class ChildLogic {
  config(amount) {
    return { meta: { amount } };
  }
}
class RootLogic {
  children() {
    return {
      modelField: child(ChildLogic, 10)
      // `config` of `ChildLogic` will be invoked with
      // `10` as a first argument
    }
  }
}

Returns Object Object that contains a logic class and an arguments array that should be used to invoke config method of provided logic class.

depends

Function that helps to describe a computed field with external model dependncies. Should be used in LogicBase#computed. It creates an instance of ComputedField with dependencies passed as arguments.

A compute field with external dependencies is a field that should be updated (re-computed) not only when the own model udpated, but also when depdendency models updated.

Parameters

  • deps ...deps A list of models with attached logic

Examples

class ChildLogic {
  // some logic
}
class MyLogic {
  children() {
    return { childModel: ChildLogic };
  }
  computed() {
    return {
      // depends on `childModel`
      computedField: depends(this.model.childModel).compute(() => {
        return this.model.a + this.model.childModel.b;
      }),
 
      // depends on `shared` model
      computeWithShared: depends(this.shared).compute(() => {
        return this.model.b + this.shared.someField;
      })
    }
  }
}

Returns ComputedField

Extras

Other pieces of MangoJuice

Process

The main class of MangoJuice that ties model, logic and view together.

It works in the following way:

  • You create a model for your app (object) using createModel of the root block. The result is a plain object.
  • Then you need to create an instance of Process and pass logic class in the options object.
  • Then you bind the Process instance to the model object you created on step one. "To bind Process to a model" means that in the model will be created a hidden field __proc with a reference to the Process instance.
  • After that you can run the Process, which will execute init commands, port.

bind and run also look at the object returned from LogicBase#children and create/run Process instances for children models.

Most of the time you do not need to care about all of these and just use run and mount. These small functions do everything described above for you.

Examples

import { Process, logicOf } from 'mangojuice-core';
 
const ExampleBlock = {
  createModel() {
    return { a: 10 };
  },
  Logic: class Example {
    \@cmd Increment(amount = 1) {
      return { a: this.model.a + amount };
    }
  }
};
 
const model = ExampleBlock.createModel();
const proc = new Process({ logic: ExampleBlock.Logic });
proc.bind(model);
proc.run();
proc.exec(logicOf(model).Increment(23));
bind

Bind the process instance to a given model, which means that hidden __proc field will be created in the model with a reference to the Process instance.

Also bind all children models – go through children definition returned by LogicBase#children, create Process for each child and bind it to an appropreat child model object.

Parameters

  • model Object A model object that you want to bind to the Process.
run

Run the process – run children processes, then run port and init commands defined in config. Also run all model observers created by observe

destroy

Destroy the process with unbinding from the model and cleaning up all the parts of the process (stop ports, computed fields). __proc field will be removed from the model object and all children objects.

Parameters

  • deep Boolean If true then all children blocks will be destroyed. By default, if not provided then considered as true.
exec

Exec a command in scope of the Process instance. It will use binded model and logic object to run the command. The command could be function (command factory) or object (Command instance).

A command origin function could return three types of things:

  • Another Command/command factory or an array of commands (an element of the array also could be another array with commands or null/undefined/false). All commands will be execute in provided order.
  • An instance of TaskMeta (which is usually created by task helper function). The returned task will be started and do not block execution of next commands in the stack.
  • A plain object which is a model update object. The object will be merged with current model.

If command is undefined or Process instance not binded to any model it will do nothing.

Parameters

Examples

class MyLogic {
  \@cmd BatchCommand() {
    return [
      1 > 2 && this.SomeCoomand,
      this.AnotheCommand(123),
      2 > 1 && [
        this.AndSomeOtherCommand,
        this.FinalCommand()
      ]
    ];
  }
  \@cmd TaskCommand() {
    return task(Tasks.SomeTask)
      .success(this.SuccessCommand)
      .fail(this.FailCommand)
  }
  \@cmd ModelUpdateCommand() {
    if (Math.random() > 0.6) {
      return { field: Date.now() };
    }
  }
}
finished

Returns a promise which will be resolved when all async tasks and related handlers in the process and all children processes will be finished.

Returns Promise

Command

Class which declares a command that will be executed in the future by some Process#exec. It contains a function that will be executed, arguments that should be passed to the function and command name.

The command also keep a context that should be used to execute a function. Usually this context is some logic instance. You can bind a context to the command using Command#bind function.

The command instance is immutable, so any function that makes some change in the command will produce a new command instead of changing the original one.

Parameters

Properties

  • func Function An origin function that should be executed
  • args Array<any> A set of arguments that should be used to execute the fucntion.
exec

Execute a function stored in the command instance. It passes stored arguments to the function and call it in stored context. Returns the value that was returned form the function.

Returns any

clone

Clone instance of the command

Returns Command

is

Check is the command equal to some other command or have a reference to the same origin function. Optionally you can also check that the command binded to some concrete model (using the second argument).

You can use many different formats to define the command to check:

  • Just a string, which should be constructed like Logic Class Name + Function Name
  • Command object or command factory that you can get from prorotype of some logic class.
  • Binded command object or command factory that you can get from concrete model using logicOf function

Parameters

  • cmd (Command | string) Command object or command factory or command name
  • childModel Object? Model object that should be binded to the command (optional)

Examples

class ChildLogic {
  \@cmd SomeCommand() {}
}
class MyLogic {
  hubAfter(cmd) {
    if (cmd.is('ChildLogic.SomeCommand')) {
      // by command name
    }
    if (cmd.is(ChildLogic.prorotype.SomeCommand)) {
      // by command factory
    }
    if (cmd.is(logicOf(this.model.child).SomeCommand)) {
      // by binded to a concrete model command factory (commands
      // binded for other models will be ignored)
    }
    if (cmd.is(ChildLogic.prorotype.SomeCommand, this.model.child)) {
      // by command factory and concrete child model (commands
      // binded for other models will be ignored)
    }
  }
}

Returns Boolean Returns true if the command have same name, or same origin function and binded to same model (if second argument provided)

appendArgs

Creates a new Command instance (clone the command) and append given list of arguments to the current list of arguments. Returns a new Command with new list of arguments.

Parameters

  • args Array<any> List of arguments that will be appened to tne new Command

Returns Command New command with appended arguments

bind

Creates a new Command instance and set given logic instance in it. Also update command name by name of the origin function and name of the logic class.

Parameters

  • logic LogicBase A logic instance that should be binded to a command

Returns Command New command binded to given logic instance

TaskMeta

Class for declaring an async task. An instance of this object returned from taks function.

A task function is a function that could return a Promise. The resolved value will be passed to a success command handler, the rejected command will be passed to a fail command handler.

If task function do not return a Promise, then the returned value passed to a success command, and if the function throw an error then the error passed to a fail command.

The task function receive at least one argument – an object with model, shared and meta. All the next arguments will be given from a command that returned a task, if it is not overrided by TaskMeta#args

By default a task is single-threaded. It means that every call to a task will cancel previously running task if it is running. You can make the task to be multi-threaded by TaskMeta#multithread, so every call to a task won't cancel the running one.

In a context of the task function defined special call function. This function should be used to invoke sub-tasks. It is important to use call to run sub-tasks because call create a cancellation point of the task – if the task cancelled, then nothing will be executed after currently running call sub-task.

call returns an object with result and error fields. If error field is not empty, then something weird happened in the sub-task. call internally have a try/catch, so it is not necessary to wrap it with try/catch, just check the error field in the returned object and if it is not empty – handle it somehow (throw it, or do any custom error handling)

Also in a context of the task defined a notify function. It is a function that execute notify command if it is defined. The command executed with the same arguments as passed to notify function. You can use it to incrementally update a model while the long task is executing, to show some progress for the user.

Parameters

Examples

async function SubTask(a, b, c) {
  this.notify(+ b + c);
  await this.call(delay, 100);
  return (/ b * c);
}
async function RootTask({ model }) {
  const { result, error } = await this.call(SubTask, 1, 0, 3);
  if (error) {
    throw new Error('Something weird happened in subtask')
  }
  return result + 10;
}
class MyLogic {
  \@cmd AsyncCommand() {
    return task(Tasks.RootTask)
      .success(this.SuccessCommand)
      .fail(this.FailCommand)
      .notify(this.NotifyCommand)
  }
}
notify

Set a notify handler command. This command executed by call of this.notify inside a task with the same arguments as passed to this.notify

Parameters

Returns TaskMeta

success

Set a success handler command. Will be executed with a value returned from the task, or if the task returned a Promise – with resovled value.

Parameters

Returns TaskMeta

fail

Set a fail handler command. Will be executed with error throwed in the task, or if the task returned a Promise – with rejected value.

Parameters

Returns TaskMeta

multithread

Define the task to be "multi-thread", so every call will run in parallel with other calls of the same task in scope of one process (model).

Parameters

Returns TaskMeta

engine

Set task executor function. The executor function should return an object that should have at least two fields: exec and cancel functions. exec should return a promise which should be resolved or rejected with an object { result, error }. A cancel function should stop the task execution.

Parameters

  • engine Function A function that returns { exec: Function, cancel: Function } object

Returns TaskMeta

args

Override arguments which will be passed to the task starting from second argument. By default a task will receive the same set of arguments as a task command. If this function invoked, then the task will receive given arguments instead of command arguments.

Parameters

  • args ...any

Returns TaskMeta

DefaultLogger

A class which defins a logger for tracking command execution process. The default implmenetation just defines all possible logger methods (log points) and print errors to the console. Extend this class to define your own logging functionality (like logging errors with Setnry or Loggly)

Examples

// ./index.js
import { DefaultLogger, run } from 'mangojuice-core';
import MainBlock from './blocks/MainBlock';
 
// Implement custom logger
class SetnryLogger extends DefaultLogger {
  onCatchError(error, cmd) {
    Raven.catchException(error);
  }
}
 
// Pass instance of the Logger to `run` options object.
run(MainBlock, { logger: new SetnryLogger() });
onStartExec

This method invoked right before anything else - as the first thing in Process#exec. Even before any LogicBase#hubBefore.

Parameters

  • cmd Command Command that just started execution process
onStartHandling

Invoked right before command will go through all LogicBase#hubBefore or LogicBase#hubAfter up in the blocks tree. The second argument indicates is it called for LogicBase#hubBefore or for LogicBase#hubAfter.

Parameters

  • cmd Command Command that is going to go through hubs in parent blocks
  • isAfter Boolean If true then the command is going through "after" hubs, "before" otherwise
onEndHandling

Invoked when the command went through all hubs and when all sync commands returned from hubs was executed.

Parameters

  • cmd Command Command that went through all hubs
  • isAfter Boolean If true then the command going through "after" hubs, "before" otherwise
onExecuted

Invoked when the command function executed and the return of the function processed – all returned commands executed, all async tasks started, the model updated. But it invoked before the command go through "after" hubs.

Parameters

  • cmd Command Command that was exectued and the return processed
  • result any Returned object from the command's function
onEndExec

Invoked right after the command go throught "after" hubs. It is the final stage of command execution.

Parameters

  • cmd Command Command that was fully executed and handled by parent blocks
onCatchError

Invoked if any uncaught error happened while execution of the command or anywhere else in the logic, like in LogicBase#port or in LogicBase#computed. By default print the error using console.error.

Parameters

  • error Error The error instance
  • cmd Command? Command that was executing while the error happened

ComputedField

Class for declaring computed field with model dependencies. Given array shuold contain models, binded to some logec. They will be passed to compute function in the same order. Also they will be used to track changed and re-compute.

The class is immutable, so any call to any method will create a new instance of ComputedField and the old one won't be touched.

Parameters

Properties

  • deps Array<Object> An array of models with binded logic (attached some Process)
  • computeFn Function A compute function that will be used to compute value of the computed field.
compute

Creates a new instance of the field and set compute function in it.

Parameters

  • func Function A compute function that should return a value that will be used as a value for some computed field in a model. This function will be invoked with all dependency models as arguments

Examples

// A way to override computed field of some base logic class
// (thanks to immutability of `ComputedField`)
class MyLogic extends SomeOtherLogic {
  computed() {
    const oldComputed = super.computed();
    return {
      ...oldComputed,
 
      // Override `oldField` compute function with saved dependencies
      // and use overrided compute function inside new compute function
      oldField: oldComputed.compute((...deps) => {
        return 1 > 0 || oldComputed.oldField.computeFn(...deps);
      })
    };
  }
}

Returns ComputedField New instance of the ComputedField with computeFn set to given function.

Mounter

To use mount function you need to implement a Mounter interface which should have mount and unmount functions. It is up to the developer how these functions will be implemented and what view library they will use.

There is a rule that mounter and view library should follow: view of a model should be updated only when the model updated and the update shouldn't touch views of children models (children views shouldn't be updated because their models is not changed).

React perfectly fits for this rule. By implementing shuoldComponentUpdate you can control when the component should be updated and when shouldn't. In this case this function should always return false and componenent should be updated using forceUpdate only when the model is updated (using observe).

Properties

  • mount Function A function that should expect two arguments: model object with attached Process instance and Block#View. It should render the model using given View (somehow).
  • unmount Function A function that shuold unmount mounted view from DOM.

Examples

// Minimal react mounter which follow the rules
class ViewWrapper extends React.Component {
  componentDidMount() {
    const { model } = this.props;
    this.stopObserver = observe(model, () => this.forceUpdate())
  }
  componentWillUnmount() {
    this.stopObserver();
  }
  shouldComponentUpdate() {
    return false;
  }
  render() {
    const { View, model } = this.props;
    const Logic = logicOf(model);
    return <View model={model} Logic={Logic} />
  }
}
class ReactMounter {
  mount(model, View) {
    return React.render(
      <ViewWrapper View={View} model={model} />,
      document.querySelector('#container')
    );
  }
  unmount() {
    return React.unmountComponentAtNode(
      document.querySelector('#container')
    );
  }
}

defineCommand

Provides a way to define a command in the prototype without usign a decorator. You should give a prototype of the logic class, name of the function which should be converted to a command factory and the decorator function (optional).

If the decorator function is not provided, then cmd will be used by default.

Parameters

  • proto Object Logic class prototype
  • name string Method name that you want to convert to a command factory
  • decorator function? Optional decorator function

Examples

class MyLogic {
  SomeCommand() {
    return { field: this.model.field + 1 };
  }
  // throttle 100
  ThrottledCommand() {
    return this.SomeCommand();
  }
}
 
defineCommand(MyLogic.prototype, 'SomeCommand');
defineCommand(MyLogic.prototype, 'ThrottledCommand', cmd({ throttle: 100 }));

ensureCommand

Returns a Command instance or throw an error if given argument can't be used to create a Command (is not a Command or command factory).

Parameters

  • cmd (Command | function) Command instance or command factory. When command factory then it will be invoked with no arguments to create a command

Examples

class MyLogic() {
  \@cmd SomeCommand() {
    // do something
  }
}
 
const logic = new MyLogic();
ensureCommand(logic.SomeCommand) // returns Command instance
// equals to...
logic.SomeCommand()

Returns Command

decorateLogic

By given logic class go through prototypes chain and decorate all cmd functions. Use it if you can't use decorators in your project

It determine that the function should be converted to a command factory (decorated by cmd) by its name. If the function starts with uppercase letter or underscore+upper-letter, then it is considered as a command.

Parameters

  • LogicClass LogicBase Logic class that you want to decorate
  • deep bool If true then it will go though prototypes chain and decorate every prototype in the chain.

Examples

class MyLogic {
  SomeCommand() {
  }
  _SomePrivateCommand() {
  }
  notACommand() {
    return 123;
  }
}
 
// `SomeCommand` and `_SomePrivateCommand` will be decorated with `cmd`
// (the prorotype will be changed in-place). `notACommand` won't
// be decorated.
decorateLogic(MyLogic);
 
const logic = new MyLogic();
logic.SomeCommand() // returns Command instance
logic._SomePrivateCommand() // returns Command instance
logic.notACommand() // returns 123

handleBefore

Function adds a handler to the Process attached to a given model that will be invoked before every command running for this model. The handler won't be invoked for commands from children models of given model.

Returns a function that removes the handler from the Process (stop handling)

Parameters

  • model Object A model object with attached Process
  • handler Function A function that will be called before every own command

Examples

import { handleBefore, run, logicOf } from 'mangojuice-core';
 
// Define root and child logic
class ChildLogic {
  \@cmd ChildCommand() {}
}
class MyLogic {
  children() {
    return { childModel: ChildLogic };
  }
  \@cmd RootCommand() {}
}
 
// Run block with empty models
const res = run({
  Logic: MyLogic,
  createModel: () => ({ childModel: {} })
});
 
// Adds a handler to a root model
handleBefore(res.model, (cmd) => console.log(cmd));
 
// Run commands on root and child models
res.proc.exec(logicOf(res.model).RootCommand);
res.proc.exec(logicOf(res.model.childModel).ChildCommand);
 
// In the console will be printed only `RootCommand` command object
// right before the command will be executed

Returns Function A function that stopps the handler

handleAfter

It is the same as handleBefore but the handler will be invoked after every own command.

Parameters

  • model Object A model object with attached Process
  • handler Function A function that will be called after every own command

Returns Function A function that stopps the handler

handle

Alias for handleAfter

Parameters

  • model Object A model object with attached Process
  • handler Function A function that will be called after every own command

Returns Function A function that stopps the handler

logicOf

Returns a Logic instance attached to a given model object. The logic instance stored in attached Process instance, so it execute procOf for the model to get a Process instance and then get the logic instance form a Process and return it.

If Process is not attached to the model, then an Error will be throwed. If the second argument passed then it will also check that the logic instance is instance of some particular class.

Parameters

  • model Object A model with attached Process
  • logicClass Class? Optional logic class to check that the logic instance is instance of the class

Returns LogicBase Returns a logic instance

observe

A function that adds a handler to the Process instance attached to a given model, that will be invoked on every command that changed the model. Useful for tracking changes to re-render a view of the model.

Parameters

  • model Object A model with attached Process instance
  • handler Function A function that will be invoked after every command that changed the model.
  • options Object? An object with options
    • options.batched bool If true then the handler will be invoked only when the commands stack is empty and model was changed during the stack execution. Useful to reduce amount of not necessary view re-renderings.

Examples

import { run, cmd, observe, logicOf } from 'mangojuice-core';
 
class MyLogic {
  \@cmd MultipleUpdates() {
    return [
      this.UpdateOne,
      this.UpdateTwo
    ];
  }
  \@cmd UpdateOne() {
    return { one: this.model.one + 1 };
  }
  \@cmd UpdateTwo() {
    return { two: this.model.two + 1 };
  }
}
 
const res = run({
  Logic: MyLogic,
  createModel: () => ({ one: 1, two: 1 })
});
 
observe(res.model, () => console.log('not-batched'));
observe(res.model, () => console.log('batched'), { batched: true });
 
res.proc.exec(logicOf(res.model).MultipleUpdates);
 
// `not-batched` will be printed two times
// `batched` will be printed only once

Returns Function

procOf

Get a Process instance attached to the given model object. Internally it get a __proc field from the given model and returns it.

If given model do not have attached Process, then an Error will be throwed, but only if the second argument is not true, which ignores the error.

Parameters

  • model Object A model with attached Process
  • ignoreError bool If true then no error will be throwed if Process is not attached to the model

Returns Process An instance of Process attached to the model

bind

This function do the following:

Returns an object with created process instance, model object and given block. Usefull when you want to prepare the block to run but you want to run it manually.

Parameters

  • block Block A Block object
  • opts Object? Options object that could change Process behaviour (optional, default {})
    • opts.logger DefaultLogger? A custom logger instance that will be used in the Process to track commands execution. By default DefaultLogger will be used.
    • opts.shared Object? An object that will be available in any logic in the tree as this.shared. Could be anything you want, but you will get more benifites if you will pass model object with attached Process as shared object to be able to make computed fields with depending of shared model.
    • opts.args Array<any>? An array of arguments that will be passed to LogicBase#config of the logic.
    • opts.Process Process? A class that will be used instead of Process. By default general Process is used, but you can customize it for your specific needs and pass here. Then every other process in the tree will be an instance of this custom Process implementation

Returns {proc: Process, model: Object, block: Block} An object with Process instance, model object and block that was passed to the function

hydrate

This function replace a createModel function in given block with new function that just returns a given model. It is useful when you have some old model and just want to run everytihing with it (for example for server rendering, or hot module replacement)

Parameters

  • block Block An original block
  • model Object A model object that you want to use

Examples

// Hot module replacement example
import MyBlock from './MyBlock';
import { hydrate, run } from 'mangojuice-core';
 
// Run initial block
let res = run(MyBlock);
 
// When block changed destroy the old process, hydrate a new
// block with existing model and run hydrated new block again
module.hot.accept(['./MyBlock'], () => {
  res.proc.destroy();
  const newBlock = hydrate(require('./MyBlock'), res.model);
  res = run(newBlock);
});

Returns Block A new block with replaced createModel function which just returns a model that you passed as second argument.

delay

A helper function for delaying execution. Returns a Promise which will be resolved in given amount of milliseconds. You can use it in task to implement some delay in execution, for debouncing for example.

Parameters

  • ms number An amount of milliseconds to wait

Returns Promise A promise that resolved after given amount of milliseconds

Basic

The most common APIs that you will work with most of the time

Block

Block is a top level component in MangoJuice which consists of three parts:

  • Model factory function createModel which should return a plain object. The main requirement for this function is that it should be able to return some model with zero arguments. But it can also accept some arguments as well.
  • A logic class which implements LogicBase interface
  • A View function which is aimed to render the model created by createModel

The View part is optional and it could have different type depending on selected View library to render a model and particular binding library.

Properties

  • Logic LogicBase A logic class that should be attached to the model.
  • createModel Function A function that shuld return a plain object which will be an init model of the block.
  • View any? Something that some particular view library can render

Examples

// You can define block in short way using an object
const MyBlock = {
  createModel: () => ({ field: 1 }),
  Logic: class MyLogic {
    \@cmd SomeCommand() {
      return { field: this.model.field + 1 };
    }
  },
  View: ({ model }) => (
    <div>{model.field}</div>
  )
};
 
run(MyBlock);
// Or you can split it with modules (recommended)
// ./MyBlock/index.js
import Logic from './Logic';
import View from './View';
import { createModel } from './Model';
export default { View, Logic, createModel };
export { View, Logic, createModel };
export type { Model } from './Model';
 
// ./MyBlock/Logic.js
import type { Model } from './Model';
import { LogicBase, cmd } from 'mangojuice-core';
 
export default class MyLogic extends LogicBase<Model> {
  \@cmd SomeCommand() {
    return { field: this.model.field + 1 };
  }
}
 
// ./MyBlock/Model.js
export type Model = { fieldnumber };
export const createModel = () => ({
  field: 1
});
 
// ./MyBlock/View.js
const MyBlockView = ({ model }) => (
  <div>{model.field}</div>
);
export default MyBlockView;
 
// ./index.js
import MyBlock from './MyBlock';
import { run } from 'mangojuice-core';
 
run(MyBlock);

LogicBase

Basic class for every logic. It is not necessaty to inherit an actual logic class from it, but for better type-checking it could be really usefull.

Logic class defines:

The logic class defines all the business-logic of some part of your application. The main goal of the logic – change associated model. Check cmd and Process#exec to have a better understanding how command can be defined and how it is executed.

Properties

  • model Object A model object which is associated with the logic class in parent logic.
  • meta Object An object defined in LogicBase#config method for storing some internal logic state.
  • shared Object An object with some shared state, defined while run process of the app.

Examples

// ./blocks/MyBlock/Logic.js
// @flow
import type { Model } from './Model';
import ChildBlock from '../ChildBlock';
import { Command, cmd } from 'mangojuice-core';
 
export default class MyLogic extends LogicBase<Model> {
  children() {
    return { child: ChildBlock.Logic };
  }
  hubAfter(cmd: Command) {
    if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
      return this.TestCommand(123);
    }
  }
  \@cmd TestCommand(arg) {
    return { field: arg + this.model.field };
  }
}

config

A function that defines init commands and meta object of the logic. Can accept any type and number of arguments. Config will take arguments passed to child function (for example child(MyLogic, 1, 2, 3) defines that the config method of MyLogic will be invoked like config(1, 2, 3))

Could return an object with the fields:

  • initCommands which could be an array or a single Command, that will be executed every time when the logic instance created and associated with some model (when Process#run invoked)
  • meta which could be an object with some internal state of the logic

Examples

config(props) {
  return {
    initCommands: [ this.StartThis, this.SendThat(123) ],
    meta: { something: props.amount }
  };
}

children

This function defines what logic class should be associated with which model field. Should return an object where a key is a model field name, and a value is a logic class or an object that defines logic with arguments (return from child function)

Examples

children() {
  return {
    searchForm: child(SearchForm.Logic, { timeout: 100 }),
    searchResults: SearchResults.Logic
  };
}

Returns Object

computed

Should return an object which defines computed fields of the model. A key of the object is a model field, and value is a function or computed function definition object (return from depends function).

Computed field is lazy, which means that it is computed on the first use of the field and the computation result cached until the next update of the model.

If you want to define a computed field which ueses in computation not only fields from the own model, but maybe some values from child model, or from shared model, then you will need to use depends function to define what models used while computation to observe changes of these models to trigger an update event of the own model (to update views).

Examples

computed() {
  return {
    simple: () => this.model.a + this.model.b,
    withDeps: depends(this.model.child, this.shared).compute(() => {
      return this.model.child.a + this.shared.b + this.model.c;
    })
  };
}

Returns Object

port

A function to handle global event, such as browser-level events, websocket, intervals. The main porpouse of the port is to subscribe to some global events, execute appropriate commands from the logic on the events and remove the handlers when the logic was destroyed.

Parameters

  • exec Function It is a function that execute a passed command
  • destroyed Promise A promise that will be resolved when the logic destroyed

Examples

port(exec, destroyed) {
  const timer = setInterval(() => {
    exec(this.SecondPassed);
  }, 1000);
  destroyed.then(() => clearInterval(timer));
}

hubAfter

Hub for all commands executed in the children blocks. The only arguments is a command object that was executed. Could return a Command or an array of Commands that should be executed next. Most of the time this function will look like a switch, but maybe with some additional rules for the cases. Take a look to Command#is function to have a better understanding how you can determine that the command is exactly what you are waiting for.

Parameters

  • cmd Command Command that was executed in some child logic or in any child of the child and so on down to the leaf of the tree.

Examples

hubAfter(cmd) {
  if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
    return this.HandlerCommand(123);
  }
  if (cmd.is('AnotherChild.SomeAnothercommand')) {
    return [ this.HandlerCommand(321), this.AnotherHandler ];
  }
}

Returns (Command? | Array<Command>?)

hubBefore

Same as LogicBase#hubAfter, but catch commands before it will be actually executed, so you can compare prev and next model state for example.

Parameters

Returns (Command? | Array<Command>?)

cmd

Decorator that converts a logic method to a command factory. The result function (command factory function) is a function that return a Command instnace with the decorated function and arguments that you will pass to a command factory function.

You can use this decorator without arguments or with an options argument (object)

Check Process#exec to see what the origin function could return to do something. If in short, it can return: nothing, command (or factory or array of commands), task, model update object.

Parameters

  • obj Object If only this argument passed then it will be considered as options object which can customize command behaviour.
    • obj.debounce number If greeter than zero then an exeuction of the command will be debounced by given amount of milliseconds
    • obj.throttle number If greeter than zero then an exeuction of the command will be throttled by given amount of milliseconds
    • obj.noInitCall bool If true then debounced/throttled execution won't start with init command call. By default debounced/throttled command will be instantly exected and only then wait for a chance to execute again. With noInitCall=true there is no instant init command call.
    • obj.internal bool Make the command invisible for parent LogicBase#hubBefore and LogicBase#hubAfter. Use it for commands that shouldn't be handled by hubs to increse performance.
    • obj.name string By default name of the command taken from origin function. With this option you can override the name with any other value.
  • methodName
  • descr

Examples

class RegularLogic {
  \@cmd RegularCommand() {
    // do something
  }
}
class RegularLogic {
  \@cmd({ debounce: 300 })
  DebouncedCommand() {
    // if you will call this command 3 times every
    // 100ms then it will be executed 2 times,
    // first right when you call it for a first time,
    // and second in 600ms.
  }
}
class RegularLogic {
  \@cmd({ throttle: 300 })
  ThrottledCommand() {
    // if you will call this command 3 times every
    // 100ms then it will be executed 2 times,
    // first right when you call it for a first time,
    // and second in 300ms.
  }
}
class RegularLogic {
  \@cmd({ throttle: 300, noInitCall: true })
  ThrottledCommand() {
    // if you will call this command 3 times every
    // 100ms then it will be executed 1 time in 300ms.
  }
}
class RegularLogic {
  \@cmd({ internal: true })
  _InternalCommand() {
    // this command can't be caught in `hubBefore` or `hubAfter`
    // in parent logics. Use it carefully.
  }
}

Returns (Function | object) Command factory function (if all three arguemnts passed) or a decorator function with binded options object (that you pass as a first argument)

run

Run given block. It is a short-hand function for running bind and then running a Process by Process#run. Also it returns an additional finished Promise which is resolved when all async tasks finished and all handler commands executed.

Parameters

  • block Block A block that you wan to run
  • opts Object Same options as in bind (optional, default {})

Returns {proc: Process, model: Object, block: Block, finished: Promise} An object which is almost the same as returned from bind, but with additional finished field, which is a Promise that will be resolved when all blocks will execute all commands and all async tasks will be finished.

mount

Mount running block. As third and next arguments you can pass other running blocks which will be stopped with the main running block (useful for HMR).

Parameters

Examples

const MyBlock = {
  createModel: () => ({ field: 1 }),
  Logic: class MyLogic {
    \@cmd SomeCommand() {
      return { field: this.model.field + 1 };
    }
  },
  View: ({ model }) => (
    <div>{model.field}</div>
  )
};
 
mount(new ReactMounter(), run(MyBlock));

Returns {view: any, stop: Function} An object with the result form Mounter#mount function (in view field) and stop function which destroy the process and unmount a view using Mounter#unmount

task

Creates a TaskMeta object that could be returned from async task command. It describes the task that should be executed.

For more information what the Task is and how it should be implemented take a look to TaskMeta.

Parameters

  • taskFn Function A function that could return a Promise

Properties

  • CANCEL string A symbol that you can use to make a promise "cancellable"

Returns Object

cancel

This function should be used to cancel async task execution. Also it can cancel the debounced/throttled command.

  • In case with task, if task was executing while you call a cancel command, the task will be cancelled (execution stopped). Otherwise nothing will happen.
  • In case with debounced/throttled command, if the command scheduled to be executed then the schedule timer will be cleared and the command won't be executed. If the command is not scheduled then nothing happens.

To use it just pass a command or command factory as a first argument and return it from some other command.

Parameters

  • cmd (Command | function) Command or command factory that you wan to cancel. Could be a command that returns task or debounced/throttled command

Examples

class SomeLogic {
  \@cmd CancelSomethingCommand() {
    return [
      cancel(this.TaskCommand),
      cancel(this.DebounceCommand)
    ];
  }
  \@cmd TaskCommand() {
    return task(Tasks.SomeTask);
  }
  \@cmd({ debounce: 300 })
  DebounceCommand() {
    // do something
  }
}

Returns Command Returns a new command that will cancel the execution of the command passed as an argument.

child

Creates an object which describes child logic which will be attached to some model field. Should be used in LogicBase#children to define a logic that should be instantiated with some arguments passed to LogicBase#config.

Parameters

Examples

class ChildLogic {
  config(amount) {
    return { meta: { amount } };
  }
}
class RootLogic {
  children() {
    return {
      modelField: child(ChildLogic, 10)
      // `config` of `ChildLogic` will be invoked with
      // `10` as a first argument
    }
  }
}

Returns Object Object that contains a logic class and an arguments array that should be used to invoke config method of provided logic class.

depends

Function that helps to describe a computed field with external model dependncies. Should be used in LogicBase#computed. It creates an instance of ComputedField with dependencies passed as arguments.

A compute field with external dependencies is a field that should be updated (re-computed) not only when the own model udpated, but also when depdendency models updated.

Parameters

  • deps ...deps A list of models with attached logic

Examples

class ChildLogic {
  // some logic
}
class MyLogic {
  children() {
    return { childModel: ChildLogic };
  }
  computed() {
    return {
      // depends on `childModel`
      computedField: depends(this.model.childModel).compute(() => {
        return this.model.a + this.model.childModel.b;
      }),
 
      // depends on `shared` model
      computeWithShared: depends(this.shared).compute(() => {
        return this.model.b + this.shared.someField;
      })
    }
  }
}

Returns ComputedField

Extras

Other pieces of MangoJuice

Process

The main class of MangoJuice that ties model, logic and view together.

It works in the following way:

  • You create a model for your app (object) using createModel of the root block. The result is a plain object.
  • Then you need to create an instance of Process and pass logic class in the options object.
  • Then you bind the Process instance to the model object you created on step one. "To bind Process to a model" means that in the model will be created a hidden field __proc with a reference to the Process instance.
  • After that you can run the Process, which will execute init commands, port.

bind and run also look at the object returned from LogicBase#children and create/run Process instances for children models.

Most of the time you do not need to care about all of these and just use run and mount. These small functions do everything described above for you.

Examples

import { Process, logicOf } from 'mangojuice-core';
 
const ExampleBlock = {
  createModel() {
    return { a: 10 };
  },
  Logic: class Example {
    \@cmd Increment(amount = 1) {
      return { a: this.model.a + amount };
    }
  }
};
 
const model = ExampleBlock.createModel();
const proc = new Process({ logic: ExampleBlock.Logic });
proc.bind(model);
proc.run();
proc.exec(logicOf(model).Increment(23));

bind

Bind the process instance to a given model, which means that hidden __proc field will be created in the model with a reference to the Process instance.

Also bind all children models – go through children definition returned by LogicBase#children, create Process for each child and bind it to an appropreat child model object.

Parameters

  • model Object A model object that you want to bind to the Process.

run

Run the process – run children processes, then run port and init commands defined in config. Also run all model observers created by observe

destroy

Destroy the process with unbinding from the model and cleaning up all the parts of the process (stop ports, computed fields). __proc field will be removed from the model object and all children objects.

Parameters

  • deep Boolean If true then all children blocks will be destroyed. By default, if not provided then considered as true.

exec

Exec a command in scope of the Process instance. It will use binded model and logic object to run the command. The command could be function (command factory) or object (Command instance).

A command origin function could return three types of things:

  • Another Command/command factory or an array of commands (an element of the array also could be another array with commands or null/undefined/false). All commands will be execute in provided order.
  • An instance of TaskMeta (which is usually created by task helper function). The returned task will be started and do not block execution of next commands in the stack.
  • A plain object which is a model update object. The object will be merged with current model.

If command is undefined or Process instance not binded to any model it will do nothing.

Parameters

Examples

class MyLogic {
  \@cmd BatchCommand() {
    return [
      1 > 2 && this.SomeCoomand,
      this.AnotheCommand(123),
      2 > 1 && [
        this.AndSomeOtherCommand,
        this.FinalCommand()
      ]
    ];
  }
  \@cmd TaskCommand() {
    return task(Tasks.SomeTask)
      .success(this.SuccessCommand)
      .fail(this.FailCommand)
  }
  \@cmd ModelUpdateCommand() {
    if (Math.random() > 0.6) {
      return { field: Date.now() };
    }
  }
}

finished

Returns a promise which will be resolved when all async tasks and related handlers in the process and all children processes will be finished.

Returns Promise

Command

Class which declares a command that will be executed in the future by some Process#exec. It contains a function that will be executed, arguments that should be passed to the function and command name.

The command also keep a context that should be used to execute a function. Usually this context is some logic instance. You can bind a context to the command using Command#bind function.

The command instance is immutable, so any function that makes some change in the command will produce a new command instead of changing the original one.

Parameters

Properties

  • func Function An origin function that should be executed
  • args Array<any> A set of arguments that should be used to execute the fucntion.

exec

Execute a function stored in the command instance. It passes stored arguments to the function and call it in stored context. Returns the value that was returned form the function.

Returns any

clone

Clone instance of the command

Returns Command

is

Check is the command equal to some other command or have a reference to the same origin function. Optionally you can also check that the command binded to some concrete model (using the second argument).

You can use many different formats to define the command to check:

  • Just a string, which should be constructed like Logic Class Name + Function Name
  • Command object or command factory that you can get from prorotype of some logic class.
  • Binded command object or command factory that you can get from concrete model using logicOf function

Parameters

  • cmd (Command | string) Command object or command factory or command name
  • childModel Object? Model object that should be binded to the command (optional)

Examples

class ChildLogic {
  \@cmd SomeCommand() {}
}
class MyLogic {
  hubAfter(cmd) {
    if (cmd.is('ChildLogic.SomeCommand')) {
      // by command name
    }
    if (cmd.is(ChildLogic.prorotype.SomeCommand)) {
      // by command factory
    }
    if (cmd.is(logicOf(this.model.child).SomeCommand)) {
      // by binded to a concrete model command factory (commands
      // binded for other models will be ignored)
    }
    if (cmd.is(ChildLogic.prorotype.SomeCommand, this.model.child)) {
      // by command factory and concrete child model (commands
      // binded for other models will be ignored)
    }
  }
}

Returns Boolean Returns true if the command have same name, or same origin function and binded to same model (if second argument provided)

appendArgs

Creates a new Command instance (clone the command) and append given list of arguments to the current list of arguments. Returns a new Command with new list of arguments.

Parameters

  • args Array<any> List of arguments that will be appened to tne new Command

Returns Command New command with appended arguments

bind

Creates a new Command instance and set given logic instance in it. Also update command name by name of the origin function and name of the logic class.

Parameters

  • logic LogicBase A logic instance that should be binded to a command

Returns Command New command binded to given logic instance

TaskMeta

Class for declaring an async task. An instance of this object returned from taks function.

A task function is a function that could return a Promise. The resolved value will be passed to a success command handler, the rejected command will be passed to a fail command handler.

If task function do not return a Promise, then the returned value passed to a success command, and if the function throw an error then the error passed to a fail command.

The task function receive at least one argument – an object with model, shared and meta. All the next arguments will be given from a command that returned a task, if it is not overrided by TaskMeta#args

By default a task is single-threaded. It means that every call to a task will cancel previously running task if it is running. You can make the task to be multi-threaded by TaskMeta#multithread, so every call to a task won't cancel the running one.

In a context of the task function defined special call function. This function should be used to invoke sub-tasks. It is important to use call to run sub-tasks because call create a cancellation point of the task – if the task cancelled, then nothing will be executed after currently running call sub-task.

call returns an object with result and error fields. If error field is not empty, then something weird happened in the sub-task. call internally have a try/catch, so it is not necessary to wrap it with try/catch, just check the error field in the returned object and if it is not empty – handle it somehow (throw it, or do any custom error handling)

Also in a context of the task defined a notify function. It is a function that execute notify command if it is defined. The command executed with the same arguments as passed to notify function. You can use it to incrementally update a model while the long task is executing, to show some progress for the user.

Parameters

Examples

async function SubTask(a, b, c) {
  this.notify(+ b + c);
  await this.call(delay, 100);
  return (/ b * c);
}
async function RootTask({ model }) {
  const { result, error } = await this.call(SubTask, 1, 0, 3);
  if (error) {
    throw new Error('Something weird happened in subtask')
  }
  return result + 10;
}
class MyLogic {
  \@cmd AsyncCommand() {
    return task(Tasks.RootTask)
      .success(this.SuccessCommand)
      .fail(this.FailCommand)
      .notify(this.NotifyCommand)
  }
}

notify

Set a notify handler command. This command executed by call of this.notify inside a task with the same arguments as passed to this.notify

Parameters

Returns TaskMeta

success

Set a success handler command. Will be executed with a value returned from the task, or if the task returned a Promise – with resovled value.

Parameters

Returns TaskMeta

fail

Set a fail handler command. Will be executed with error throwed in the task, or if the task returned a Promise – with rejected value.

Parameters

Returns TaskMeta

multithread

Define the task to be "multi-thread", so every call will run in parallel with other calls of the same task in scope of one process (model).

Parameters

Returns TaskMeta

engine

Set task executor function. The executor function should return an object that should have at least two fields: exec and cancel functions. exec should return a promise which should be resolved or rejected with an object { result, error }. A cancel function should stop the task execution.

Parameters

  • engine Function A function that returns { exec: Function, cancel: Function } object

Returns TaskMeta

args

Override arguments which will be passed to the task starting from second argument. By default a task will receive the same set of arguments as a task command. If this function invoked, then the task will receive given arguments instead of command arguments.

Parameters

  • args ...any

Returns TaskMeta

DefaultLogger

A class which defins a logger for tracking command execution process. The default implmenetation just defines all possible logger methods (log points) and print errors to the console. Extend this class to define your own logging functionality (like logging errors with Setnry or Loggly)

Examples

// ./index.js
import { DefaultLogger, run } from 'mangojuice-core';
import MainBlock from './blocks/MainBlock';
 
// Implement custom logger
class SetnryLogger extends DefaultLogger {
  onCatchError(error, cmd) {
    Raven.catchException(error);
  }
}
 
// Pass instance of the Logger to `run` options object.
run(MainBlock, { logger: new SetnryLogger() });

onStartExec

This method invoked right before anything else - as the first thing in Process#exec. Even before any LogicBase#hubBefore.

Parameters

  • cmd Command Command that just started execution process

onStartHandling

Invoked right before command will go through all LogicBase#hubBefore or LogicBase#hubAfter up in the blocks tree. The second argument indicates is it called for LogicBase#hubBefore or for LogicBase#hubAfter.

Parameters

  • cmd Command Command that is going to go through hubs in parent blocks
  • isAfter Boolean If true then the command is going through "after" hubs, "before" otherwise

onEndHandling

Invoked when the command went through all hubs and when all sync commands returned from hubs was executed.

Parameters

  • cmd Command Command that went through all hubs
  • isAfter Boolean If true then the command going through "after" hubs, "before" otherwise

onExecuted

Invoked when the command function executed and the return of the function processed – all returned commands executed, all async tasks started, the model updated. But it invoked before the command go through "after" hubs.

Parameters

  • cmd Command Command that was exectued and the return processed
  • result any Returned object from the command's function

onEndExec

Invoked right after the command go throught "after" hubs. It is the final stage of command execution.

Parameters

  • cmd Command Command that was fully executed and handled by parent blocks

onCatchError

Invoked if any uncaught error happened while execution of the command or anywhere else in the logic, like in LogicBase#port or in LogicBase#computed. By default print the error using console.error.

Parameters

  • error Error The error instance
  • cmd Command? Command that was executing while the error happened

ComputedField

Class for declaring computed field with model dependencies. Given array shuold contain models, binded to some logec. They will be passed to compute function in the same order. Also they will be used to track changed and re-compute.

The class is immutable, so any call to any method will create a new instance of ComputedField and the old one won't be touched.

Parameters

Properties

  • deps Array<Object> An array of models with binded logic (attached some Process)
  • computeFn Function A compute function that will be used to compute value of the computed field.

compute

Creates a new instance of the field and set compute function in it.

Parameters

  • func Function A compute function that should return a value that will be used as a value for some computed field in a model. This function will be invoked with all dependency models as arguments

Examples

// A way to override computed field of some base logic class
// (thanks to immutability of `ComputedField`)
class MyLogic extends SomeOtherLogic {
  computed() {
    const oldComputed = super.computed();
    return {
      ...oldComputed,
 
      // Override `oldField` compute function with saved dependencies
      // and use overrided compute function inside new compute function
      oldField: oldComputed.compute((...deps) => {
        return 1 > 0 || oldComputed.oldField.computeFn(...deps);
      })
    };
  }
}

Returns ComputedField New instance of the ComputedField with computeFn set to given function.

Mounter

To use mount function you need to implement a Mounter interface which should have mount and unmount functions. It is up to the developer how these functions will be implemented and what view library they will use.

There is a rule that mounter and view library should follow: view of a model should be updated only when the model updated and the update shouldn't touch views of children models (children views shouldn't be updated because their models is not changed).

React perfectly fits for this rule. By implementing shuoldComponentUpdate you can control when the component should be updated and when shouldn't. In this case this function should always return false and componenent should be updated using forceUpdate only when the model is updated (using observe).

Properties

  • mount Function A function that should expect two arguments: model object with attached Process instance and Block#View. It should render the model using given View (somehow).
  • unmount Function A function that shuold unmount mounted view from DOM.

Examples

// Minimal react mounter which follow the rules
class ViewWrapper extends React.Component {
  componentDidMount() {
    const { model } = this.props;
    this.stopObserver = observe(model, () => this.forceUpdate())
  }
  componentWillUnmount() {
    this.stopObserver();
  }
  shouldComponentUpdate() {
    return false;
  }
  render() {
    const { View, model } = this.props;
    const Logic = logicOf(model);
    return <View model={model} Logic={Logic} />
  }
}
class ReactMounter {
  mount(model, View) {
    return React.render(
      <ViewWrapper View={View} model={model} />,
      document.querySelector('#container')
    );
  }
  unmount() {
    return React.unmountComponentAtNode(
      document.querySelector('#container')
    );
  }
}

defineCommand

Provides a way to define a command in the prototype without usign a decorator. You should give a prototype of the logic class, name of the function which should be converted to a command factory and the decorator function (optional).

If the decorator function is not provided, then cmd will be used by default.

Parameters

  • proto Object Logic class prototype
  • name string Method name that you want to convert to a command factory
  • decorator function? Optional decorator function

Examples

class MyLogic {
  SomeCommand() {
    return { field: this.model.field + 1 };
  }
  // throttle 100
  ThrottledCommand() {
    return this.SomeCommand();
  }
}
 
defineCommand(MyLogic.prototype, 'SomeCommand');
defineCommand(MyLogic.prototype, 'ThrottledCommand', cmd({ throttle: 100 }));

ensureCommand

Returns a Command instance or throw an error if given argument can't be used to create a Command (is not a Command or command factory).

Parameters

  • cmd (Command | function) Command instance or command factory. When command factory then it will be invoked with no arguments to create a command

Examples

class MyLogic() {
  \@cmd SomeCommand() {
    // do something
  }
}
 
const logic = new MyLogic();
ensureCommand(logic.SomeCommand) // returns Command instance
// equals to...
logic.SomeCommand()

Returns Command

decorateLogic

By given logic class go through prototypes chain and decorate all cmd functions. Use it if you can't use decorators in your project

It determine that the function should be converted to a command factory (decorated by cmd) by its name. If the function starts with uppercase letter or underscore+upper-letter, then it is considered as a command.

Parameters

  • LogicClass LogicBase Logic class that you want to decorate
  • deep bool If true then it will go though prototypes chain and decorate every prototype in the chain.

Examples

class MyLogic {
  SomeCommand() {
  }
  _SomePrivateCommand() {
  }
  notACommand() {
    return 123;
  }
}
 
// `SomeCommand` and `_SomePrivateCommand` will be decorated with `cmd`
// (the prorotype will be changed in-place). `notACommand` won't
// be decorated.
decorateLogic(MyLogic);
 
const logic = new MyLogic();
logic.SomeCommand() // returns Command instance
logic._SomePrivateCommand() // returns Command instance
logic.notACommand() // returns 123

handleBefore

Function adds a handler to the Process attached to a given model that will be invoked before every command running for this model. The handler won't be invoked for commands from children models of given model.

Returns a function that removes the handler from the Process (stop handling)

Parameters

  • model Object A model object with attached Process
  • handler Function A function that will be called before every own command

Examples

import { handleBefore, run, logicOf } from 'mangojuice-core';
 
// Define root and child logic
class ChildLogic {
  \@cmd ChildCommand() {}
}
class MyLogic {
  children() {
    return { childModel: ChildLogic };
  }
  \@cmd RootCommand() {}
}
 
// Run block with empty models
const res = run({
  Logic: MyLogic,
  createModel: () => ({ childModel: {} })
});
 
// Adds a handler to a root model
handleBefore(res.model, (cmd) => console.log(cmd));
 
// Run commands on root and child models
res.proc.exec(logicOf(res.model).RootCommand);
res.proc.exec(logicOf(res.model.childModel).ChildCommand);
 
// In the console will be printed only `RootCommand` command object
// right before the command will be executed

Returns Function A function that stopps the handler

handleAfter

It is the same as handleBefore but the handler will be invoked after every own command.

Parameters

  • model Object A model object with attached Process
  • handler Function A function that will be called after every own command

Returns Function A function that stopps the handler

handle

Alias for handleAfter

Parameters

  • model Object A model object with attached Process
  • handler Function A function that will be called after every own command

Returns Function A function that stopps the handler

logicOf

Returns a Logic instance attached to a given model object. The logic instance stored in attached Process instance, so it execute procOf for the model to get a Process instance and then get the logic instance form a Process and return it.

If Process is not attached to the model, then an Error will be throwed. If the second argument passed then it will also check that the logic instance is instance of some particular class.

Parameters

  • model Object A model with attached Process
  • logicClass Class? Optional logic class to check that the logic instance is instance of the class

Returns LogicBase Returns a logic instance

observe

A function that adds a handler to the Process instance attached to a given model, that will be invoked on every command that changed the model. Useful for tracking changes to re-render a view of the model.

Parameters

  • model Object A model with attached Process instance
  • handler Function A function that will be invoked after every command that changed the model.
  • options Object? An object with options
    • options.batched bool If true then the handler will be invoked only when the commands stack is empty and model was changed during the stack execution. Useful to reduce amount of not necessary view re-renderings.

Examples

import { run, cmd, observe, logicOf } from 'mangojuice-core';
 
class MyLogic {
  \@cmd MultipleUpdates() {
    return [
      this.UpdateOne,
      this.UpdateTwo
    ];
  }
  \@cmd UpdateOne() {
    return { one: this.model.one + 1 };
  }
  \@cmd UpdateTwo() {
    return { two: this.model.two + 1 };
  }
}
 
const res = run({
  Logic: MyLogic,
  createModel: () => ({ one: 1, two: 1 })
});
 
observe(res.model, () => console.log('not-batched'));
observe(res.model, () => console.log('batched'), { batched: true });
 
res.proc.exec(logicOf(res.model).MultipleUpdates);
 
// `not-batched` will be printed two times
// `batched` will be printed only once

Returns Function

procOf

Get a Process instance attached to the given model object. Internally it get a __proc field from the given model and returns it.

If given model do not have attached Process, then an Error will be throwed, but only if the second argument is not true, which ignores the error.

Parameters

  • model Object A model with attached Process
  • ignoreError bool If true then no error will be throwed if Process is not attached to the model

Returns Process An instance of Process attached to the model

bind

This function do the following:

Returns an object with created process instance, model object and given block. Usefull when you want to prepare the block to run but you want to run it manually.

Parameters

  • block Block A Block object
  • opts Object? Options object that could change Process behaviour (optional, default {})
    • opts.logger DefaultLogger? A custom logger instance that will be used in the Process to track commands execution. By default DefaultLogger will be used.
    • opts.shared Object? An object that will be available in any logic in the tree as this.shared. Could be anything you want, but you will get more benifites if you will pass model object with attached Process as shared object to be able to make computed fields with depending of shared model.
    • opts.args Array<any>? An array of arguments that will be passed to LogicBase#config of the logic.
    • opts.Process Process? A class that will be used instead of Process. By default general Process is used, but you can customize it for your specific needs and pass here. Then every other process in the tree will be an instance of this custom Process implementation

Returns {proc: Process, model: Object, block: Block} An object with Process instance, model object and block that was passed to the function

hydrate

This function replace a createModel function in given block with new function that just returns a given model. It is useful when you have some old model and just want to run everytihing with it (for example for server rendering, or hot module replacement)

Parameters

  • block Block An original block
  • model Object A model object that you want to use

Examples

// Hot module replacement example
import MyBlock from './MyBlock';
import { hydrate, run } from 'mangojuice-core';
 
// Run initial block
let res = run(MyBlock);
 
// When block changed destroy the old process, hydrate a new
// block with existing model and run hydrated new block again
module.hot.accept(['./MyBlock'], () => {
  res.proc.destroy();
  const newBlock = hydrate(require('./MyBlock'), res.model);
  res = run(newBlock);
});

Returns Block A new block with replaced createModel function which just returns a model that you passed as second argument.

delay

A helper function for delaying execution. Returns a Promise which will be resolved in given amount of milliseconds. You can use it in task to implement some delay in execution, for debouncing for example.

Parameters

  • ms number An amount of milliseconds to wait

Returns Promise A promise that resolved after given amount of milliseconds

Readme

Keywords

none

Package Sidebar

Install

npm i mangojuice-core

Weekly Downloads

43

Version

1.2.11

License

MIT

Last publish

Collaborators

  • artem.artemev