<commit_before>/**
 * Application object.
 *
 * @typedef {Object} app
 * @property {Object} [initial={}] - an object that represents the initial state.
 * If not specified, the initial state will be `{}`.
 * @property {Function} [Actions=()=>({})] - a function that creates actions, of the form
 * `update => actions`.
 * @property {Array<Function>} [services=[]] - an array of service functions, each of which
 * should be `({ state, previousState, patch }) => patch?`.
 * @property {Array<Function>} [effects=[]] - an array of effect functions, each of which
 * should be `({ state, previousState, patch, update, actions }) => void`, with the function
 * optionally calling `update` and/or `actions`.
 */

/**
 * Stream library. This works with `meiosis.simpleStream`, `flyd`, `m.stream`, or anything for
 * which you provide either a function or an object with a `stream` function to create a stream. The
 * function or object must also have a `scan` property. The returned stream must have a `map`
 * method.
 *
 * @typedef {Object|Function} StreamLib
 * @param {*} [value] - the stream's initial value.
 * @property {Function} stream - the function to create a stream, if the stream library itself is
 * not a function.
 * @property {Function} scan - the stream library's `scan` function.
 * @return {simpleStream} - the created stream.
 */

/**
 * Base helper to setup the Meiosis pattern. If you are using Mergerino, Function Patches, or Immer,
 * use their respective `setup` function instead.
 *
 * Patch is merged in to the state by default. Services have access to the state, previous state,
 * and patch, and can return a patch that further updates the state, reverts to the previous state,
 * and so on. State changes by services are available to the next services in the list.
 *
 * After the services have run and the state has been updated, effects are executed and have the
 * opportunity to trigger more updates.
 *
 * @async
 * @function meiosis.common.setup
 *
 * @param {StreamLib} stream - the stream library. This works with `meiosis.simpleStream`, `flyd`,
 * `m.stream`, or anything for which you provide either a function or an object with a `stream`
 * function to create a stream. The function or object must also have a `scan` property. The
 * returned stream must have a `map` method.
 * @param {Function} accumulator - the accumulator function.
 * @param {Function} combine - the function that combines an array of patches into one.
 * @param {app} app - the app, with optional properties.
 *
 * @returns {Object} - `{ update, states, actions }`, where `update` and `states` are streams, and
 * `actions` are the created actions.
 */
export default ({ stream, accumulator, combine, app }) => {
  if (!stream) {
    throw new Error("No stream library was specified.");
  }
  if (!accumulator) {
    throw new Error("No accumulator function was specified.");
  }
  if (!combine) {
    throw new Error("No combine function was specified.");
  }

  app = app || {};
  let { initial, Actions, services, effects } = app;
  initial = initial || {};
  services = services || [];
  effects = effects || [];

  const singlePatch = patch => (Array.isArray(patch) ? combine(patch) : patch);
  const accumulatorFn = (state, patch) => (patch ? accumulator(state, singlePatch(patch)) : state);

  const createStream = typeof stream === "function" ? stream : stream.stream;
  const scan = stream.scan;

  const update = createStream();
  const actions = (Actions || (() => ({})))(update);
  const states = createStream();

  // context is { state, patch, previousState }
  // state is optionally updated by service patches; patch and previousState never change.
  const runServices = context => {
    const updatedContext = context;

    for (let i = 0; i < services.length; i++) {
      // a service should (optionally) return a patch
      const servicePatch = services[i](updatedContext);
      updatedContext.state = accumulatorFn(updatedContext.state, servicePatch);
    }
    return updatedContext;
  };

  const contexts = scan(
    (context, patch) =>
      runServices({
        previousState: context.state,
        state: accumulatorFn(context.state, patch),
        patch
      }),
    runServices({ state: initial, previousState: {} }),
    update
  );

  contexts
    .map(context => {
      if (context.state !== states()) {
        states(context.state);
      }

      return Object.assign(context, {
        update,
        actions
      });
    })
    .map(context => {
      effects.forEach(effect => effect(context));
    });<commit_msg>Update meiosis-setup<commit_after>/**
 * Application object.
 *
 * @typedef {Object} app
 * @property {Object} [initial={}] - an object that represents the initial state.
 * If not specified, the initial state will be `{}`.
 * @property {Function} [Actions=()=>({})] - a function that creates actions, of the form
 * `update => actions`.
 * @property {Array<Function>} [services=[]] - an array of service functions, each of which
 * should be `({ state, previousState, patch }) => patch?`.
 * @property {Array<Function>} [effects=[]] - an array of effect functions, each of which
 * should be `({ state, previousState, patch, update, actions }) => void`, with the function
 * optionally calling `update` and/or `actions`.
 */

/**
 * Stream library. This works with `meiosis.simpleStream`, `flyd`, `m.stream`, or anything for
 * which you provide either a function or an object with a `stream` function to create a stream. The
 * function or object must also have a `scan` property. The returned stream must have a `map`
 * method.
 *
 * @typedef {Object|Function} StreamLib
 * @param {*} [value] - the stream's initial value.
 * @property {Function} stream - the function to create a stream, if the stream library itself is
 * not a function.
 * @property {Function} scan - the stream library's `scan` function.
 * @return {simpleStream} - the created stream.
 */

/**
 * Base helper to setup the Meiosis pattern. If you are using Mergerino, Function Patches, or Immer,
 * use their respective `setup` function instead.
 *
 * Patch is merged in to the state by default. Services have access to the state, previous state,
 * and patch, and can return a patch that further updates the state, reverts to the previous state,
 * and so on. State changes by services are available to the next services in the list.
 *
 * After the services have run and the state has been updated, effects are executed and have the
 * opportunity to trigger more updates.
 *
 * @async
 * @function meiosis.common.setup
 *
 * @param {StreamLib} stream - the stream library. This works with `meiosis.simpleStream`, `flyd`,
 * `m.stream`, or anything for which you provide either a function or an object with a `stream`
 * function to create a stream. The function or object must also have a `scan` property. The
 * returned stream must have a `map` method.
 * @param {Function} accumulator - the accumulator function.
 * @param {Function} combine - the function that combines an array of patches into one.
 * @param {app} app - the app, with optional properties.
 *
 * @returns {Object} - `{ update, states, actions }`, where `update` and `states` are streams, and
 * `actions` are the created actions.
 */
export default ({ stream, accumulator, combine, app }) => {
  if (!stream) {
    throw new Error("No stream library was specified.");
  }
  if (!accumulator) {
    throw new Error("No accumulator function was specified.");
  }
  if (!combine) {
    throw new Error("No combine function was specified.");
  }

  app = app || {};
  let { initial, Actions, services, effects } = app;
  initial = initial || {};
  services = services || [];
  effects = effects || [];

  const singlePatch = patch => (Array.isArray(patch) ? combine(patch) : patch);
  const accumulatorFn = (state, patch) => (patch ? accumulator(state, singlePatch(patch)) : state);

  const createStream = typeof stream === "function" ? stream : stream.stream;
  const scan = stream.scan;

  const update = createStream();
  const states = createStream();
  const actions = (Actions || (() => ({})))(update, states);

  // context is { state, patch, previousState }
  // state is optionally updated by service patches; patch and previousState never change.
  scan(
    (context, patch) => ({
      previousState: context.state,
      state: accumulatorFn(context.state, patch),
      patch
    }),
    { state: initial, previousState: {} },
    update
  )
    .map(context =>
      services.reduce(
        (context, service) =>
          Object.assign(context, {
            state: accumulatorFn(context.state, service(context))
          }),
        context
      )
    )
    .map(context => {
      if (context.state !== states()) {
        states(context.state);
      }

      return Object.assign(context, {
        update,
        actions
      });
    })
    .map(context => {
      effects.forEach(effect => effect(context));
    });
{'fraction_non_alphanumeric': 0.12388392857142858, 'fraction_numerical': 0.00011160714285714285, 'mean_word_length': 3.433943592281049, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6692757', 'n_tokens_mistral': 2438, 'n_tokens_neox': 2388, 'n_words': 1006}