Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@skava/modules / ___dist / observable-container / ObservableContainer.js
Size: Mime:
/**
 * @file this file is for extending this class
 * @see ./connectToData for how this class is connected to a component further
 *
 * @description @tip use "[command] + [left-mouse-click]" to open up the type definitions!
 */
import { EMPTY_OBJ, EMPTY_ARRAY, isFunction, isArray, isObj } from 'exotic'
import { ApolloQueryResult, ApolloClient, DocumentNode } from 'typings'
import { graphql } from 'react-apollo'
import {
  observable,
  computed,
  action,
  extendObservable,
  extendShallowObservable,
} from 'xmobx/mobx'
import { observer, inject } from 'xmobx/mobx-react'
// ours
import oneStorage from 'modules/persistance/local-storage'
import oneCookie from 'modules/persistance/cookies'
import oneRouter from 'modules/router'
// import OmniStore from 'state/OmniStore'
import connectToDevtools from 'modules/devtools/remotedevtools'
import { Request as OneRequest } from 'modules/oneRequest'
// local
import { tapProps } from './deps'
import { withStore } from './connectToData'
import registry from './containerRegistry'

/**
 * @description mutates to fix
 * @see autofixSafe
 * @see modules/state-tree
 */
function mutateTypesToFixMobx(types) {
  const autofixBuiltIn = property => {
    const type = types[property]
    if (type === Array) {
      types[property] = []
    }
  }
  Object.keys(types).forEach(autofixBuiltIn)
}

const intellisense = {
  get OmniStore() {
    return require('state/OmniStore').OmniStoreX
  },
  get oneRouter() {
    return oneRouter
  },
  get OneRequest() {
    return OneRequest
  },
}

/**
 *
 * @description when the view mounts or gets new data
 *              we can handle things going through
 * @event componentWillMount
 * @event componentWillReceiveProps
 *
 *
 * @alias interceptProps
 * @param {React.Props} props container.oneStore
 *
 * @return {React.Props} decorated props available to the component we are wrapping
 *
 * @note if the store has any handle props function
 *              call it, so it can change the props
 *
 *              by default, we use tapProps which adds routing props
 *              and the omnistore
 */
function handleProps(props) {
  return tapProps(this, props)
}

/**
 * !!!!!
 * @TODO there should be many classes extending this these
 *       these are observable observable container, only 1 instance like singleton pattern
 *       but only 1 `oneState` 1 `oneRouter` ever
 * !!!!!
 *
 * @tutorial https://mobx.js.org/refguide/map.html
 * @extends BaseChain
 * @see modules/oneRequest
 * @param {Mobx.observable} store
 */
class ObservableContainer {
  // could take in debug name in props...
  constructor() {
    this.named = this.constructor.debugName || this.constructor.name
    if (process.env.NODE_ENV !== 'production') {
      registry.set(this.named, this)
    }
  }

  get containers() {
    return registry
  }

  // this helps put it at the bottom of our inspector in dev tools
  get oneRouter(): typeof intellisense.oneRouter {
    return oneRouter
  }
  get oneCookie() {
    return oneCookie
  }
  get oneStorage() {
    // .cookies
    // .ls
    return oneStorage
  }
  // :-(
  get oneRequest(): typeof intellisense.OneRequest {
    return OneRequest
  }

  /**
   * graphqlyo
   * @todo
   */
  get isLoading(): boolean {
    // this.extendObservable({
    //   dynamicState: {
    //     isLoading: false,
    //   },
    // })
    return this.dynamicState.isLoading
  }
  set isLoading(isLoading: boolean) {
    if (isObj(this.dynamicState) === false) {
      this.dynamicState = observable.object()
    }
    this.dynamicState.isLoading = isLoading
  }

  // query<T>(options: WatchQueryOptions): Promise<ApolloQueryResult<T>>;
  // mutate<T>(options: MutationOptions<T>): Promise<FetchResult<T>>;
  // subscribe(options: SubscriptionOptions): Observable<any>;
  // readQuery<T>(options: DataProxy.Query): T | null;
  // readFragment<T>(options: DataProxy.Fragment): T | null;
  // writeQuery(options: DataProxy.WriteQueryOptions): void;
  // writeFragment(options: DataProxy.WriteFragmentOptions): void;
  // gql = {
  //   // CRUD
  //   query: 'QueryHere',
  //   // dynamic
  //   // variables: {},
  // }

  dynamicState: {
    isLoading: boolean,
    // yagniu
    observableQuery: {},
    previousData: {},
  }
  // === not really needed
  get previousQueryResult() {
    return (
      this.dynamicState.previousData ||
      this.dynamicState.observableQuery.getLastResult()
    )
  }
  get queryResult() {
    return this.dynamicState.observableQuery.currentResult()
  }

  // variables,
  // pollInterval,
  // query,
  // fetchPolicy,
  // errorPolicy,
  // notifyOnNetworkStatusChange,
  // this.dynamicState.observableQuery = client.watchQuery(this.queryOptions)
  // this.dynamicState.observableQueryResult = this.dynamicState.observableQuery.currentResult()
  Query: DocumentNode
  Mutate: DocumentNode
  QueryPropsMapper: Function = props => props

  // @private
  apollo(response, queryOptions) {
    const data = response.data

    // @perf @todo
    this.dynamicState.response = response
    this.dynamicState.isLoading = response.loading
    this.dynamicState.queryOptions = queryOptions

    return data
  }

  // private
  _SERVER_SIDE_RENDERING_DO_NOT_USE_ME_OR_ELSE_DOT_DOT_DOT() {
    if (this.ClientOptions && this.ClientOptions.mutation) {
      return this.mutate()
    } else {
      return this.query()
    }
  }

  // this first arg is not used........
  async mutate(mutate, configOrVariables = undefined) {
    // @todo make sure dynamicState updates
    const queryOptions =
      configOrVariables ||
      this.ClientOptions ||
      this.dynamicState.queryOptions ||
      configOrVariables
    const response = await this.client.mutate({ mutate, queryOptions })
    return this.apollo(response, queryOptions)
  }

  // @michael, first arg is not used...
  async query(query, configOrVariables = EMPTY_OBJ) {
    // this lets you pass in `variables` or the whole config
    const { Query } = this
    const defaultQueryArgs = {
      query: Query,
      // variables: configOrVariables,
      // !!! options.variables !!!
      options: configOrVariables,
    }
    const queryOptions =
      configOrVariables ||
        this.ClientOptions ||
        configOrVariables.options ||
        configOrVariables.query
        ? configOrVariables
        : defaultQueryArgs

    if (configOrVariables === EMPTY_OBJ) {
      return Promise.resolve(EMPTY_OBJ)
    }
    const response: ApolloQueryResult<Query> = await this.client.query({
      query,
      queryOptions,
    })
    return this.apollo(response, queryOptions)
  }

  /**
   * @protected
   * @description because of the way transpiling works,
   *              we cannot use static types in a decorator
   *              and we cannot use class properties,
   *              because they get auto-added to constructor + js prototype inheritance
   *              but this removes the need for importing observable +
   *
   * @param {Object} types normal javascript object or any data to make observable as properties
   * @return {ObservableContainer} @chainable
   */
  extendObservable(types = EMPTY_OBJ) {
    mutateTypesToFixMobx(types)

    // console.dev(this.named)
    extendObservable(this, { ...types })
    // this.connectToDevtools()
    return this
  }
  extendShallowObservable(types = EMPTY_OBJ) {
    mutateTypesToFixMobx(types)
    // extendShallowObservable(this, types)
    extendShallowObservable(this, { ...types })
    return this
  }

  // -------

  /**
   * @protected
   * @type {handlePropsType}
   * @see handlePropsType
   * @override this function to handle props easily
   * @param {*} props
   */
  _handleProps(props, ref) {
    // console.info('DEFAULT_HANDLEPROPS', props)
    Object.defineProperty(this, 'props', {
      enumerable: false,
      configurable: true,
      // writable: true,
      get() {
        return props
      },
    })
    Object.defineProperty(this, 'context', {
      enumerable: false,
      configurable: true,
      // writable: true,
      get() {
        return ref.context
      },
    })
    // this.props = props
    // this.context = ref.context
    if (isFunction(this.handleProps) === true) {
      return this.handleProps(props)
    } else {
      return handleProps.call(this, props)
    }
  }


  /**
   * @protected
   * @listens componentWillUnmount
   * @param {*} props
   */
  handleRemove() {
    //
  }

  /**
   * @protected
   * @listens componentWillReceiveProps
   * @param {*} props
   */
  handleUpdate(props) {
    //
  }

  // --- helper methods ---
  // --- can put state tree types here too

  /**
   * @public
   * @param {*} data
   * @return {observable}
   */
  observable(data) {
    // observable(data) {
    return observable(data)
  }

  /**
   * @public
   * @param {ReactComponent} Target
   * @return {Observer}
   */
  observer(Target) {
    // observer(Target) {
    return observer(Target)
  }

  /**
   * @public
   * @tutorial https://reactjs.org/docs/higher-order-components.html
   * @type {Function.Decorator}
   * @param {React.Component} Target
   * @return {React.Component} obserable/subscribed component connected to ^ store
   */
  connectToData = (Target): withStore => {
    // return compose(this.injectContext, withStore(this))(Target)
    return withStore(this, Target)
  }

  connectToGraph = (Target): graphql => {
    return withStore(this, Target)
  }

  // ------

  // === not needed...
  setHandleProps(fn: Function): ObservableContainer {
    // setHandleProps(fn = Function) {
    this.handleProps = fn
    return this
  }
  // @tutorial https://github.com/mobxjs/mobx-react#customizing-inject
  // @TODO spread relational stores with this function
  // @see ./deps/injectContext
  injectContext(builder) {
    /**
     * @todo withErrorBoundary
     */
    return builder
  }
}


export { ObservableContainer, registry }
export default ObservableContainer