@composi/runtime:

HOP—Higher Order Progam

Wrap One Program In Another

@composi/core's runtime does a lot of interesting things to make it just work. This happens in the runtime closure. That's where it keeps state. If you want to be able to examine your program's state, you cannot reach it because it's never exposed outside the closure. However, if you absolutely must be able to examine state during development, you can create a Higher Order Program to wrap around your program to expose its state. Here is how you would do that:

export function logState (program, stateChanged) {
  return {
    ...program,
    view (state, send) {
      stateChanged(state)
      return program.view(state, send)
    }
  }
}

You can use the above HOP to access the wrapped program's state:

import { h, render, run } from '@composi/core'
import { logState } from './logState'

// Not a full program to conserve space:
const program = {
  init() {},
  view() {},
  update() {}
}

// Wrap the previous program in the Higher Order Program:
const wrappedProgram = logState(program, state => {
  console.log(state)
})

// Run the Higher Order Program:
run(wrappedProgram)

With our program wrapped by this Higher Order Program, the program's state will get logged every time it changes.

Debugging

You can also create a Higher Order Program for debugging purposes.

export function logErrors(program) {
  // Capture the state and effect from program:
  const programState = program.init()
  const init = () => ({ hasError: false, error: null, programState })

  // Handle whatever the update returns.
  // If error, deal with it,
  // else render the default view.
  function view(state, send) {
    if (state.hasError) {
      // Here we're only logging the error.
      // You could have an alternate view to use
      // to display the error in detail.
      return console.log(state.error)
    } else {
      // Otherwise, everything is fine so render the view.
      return program.view(state.programState, send)
    }
  }

  // Intercept the program's update to trap any errors:
  function update(state, msg) {
    let updateView
    try {
      updateView = program.update(state.programState, msg)
    } catch (error) {
      return ({ ...state, hasError: true, error })
    }

    const {programState} = updateView
    return ({ ...state, programState })
  }

  // Allow program to end.
  let done
  if (program.done) {
    done = function(state) {
      return program.done(state.programState)
    }
  }

  return ({ init, update, view, done })
}

With the above HOP, we can wrap our program in the HOP for error logging:

run(logErrors(program))