datavan

4.2.8 • Public • Published

datavan: in-memory mongodb for react that can track changes and sync with server

https://img.shields.io/npm/v/datavan.svg state npm npm

Features

  • based on New React Context
  • mongodb-like find(), update() api
  • design with offline in mind
  • customizable server fetch, submit, persistent, conflict-resolve logic
  • built-in reselect-like memoizing layer
  • supports server rendering
  • code in es6, support tree-shaking

How It works?

During find(), datavan will query your local-data first. If local-data is missing, it will call your onFetch() as a side effect and update the local-data.

Table of Contents

Other Docs

Getting Started

import _ from 'lodash'
import { createDatavanContext } from 'datavan'
 
const Van = createDatavanContext({
  // defined collection called 'myUserTable'
  myUserTable: {
    onFetch(collection, query, option) {
      return Promise.resolve([{ _id: 'id', name: 'john' }])
    },
  },
})
 
render(
  <Van.Provider store={store}>
    ...
    <Van>
      {db => {
        // first call result will be undefined
        // after HTTP response, connect will be re-run
        // second result will get user object
        const users = db.find('myUserTable', { name: { $in: ['John'] } })
 
        const onClick = () => db.update('myUserTable', { name: 'John' }, { $merge: { name: 'smith' } })
 
        return (
          <button onClick={onClick}>
            {_.map(users, 'name').join()}
          </button>
        )
      }}
    </Van>
    ...
  </Van.Provider>
)

Setup

createDb

import { createDb } from 'datavan'
 
const db = createDb({
  myUserTable: {
    // id field for document (default: `_id`)
    idField: 'id',
 
    // async fetch function (default: `undefined`). Should return array or map of documents
    onFetch(query, option, collection) {
      return fetch('restful-api?name=john')
    },
 
    // cast and convert doc fields. Return: casted doc
    // NOTE only cast to primitive types that can use === to compare. (DON'T cast to Date, Object, Array or anything that cannot be JSON.stringify)
    cast(doc) {
      doc.count = parseInt(doc.count, 10)
      // Can cast to Number as count === JSON.parse(JSON.stringify(count))
      doc.arr = 'a,b'.split(',')
      // Can cast to Number as arr !== JSON.parse(JSON.stringify(arr))
      return doc
    },
 
    onInsert(doc) {
    },
 
    onLoad(doc) {
    },
 
    // generate a new tmp id string (default: genId from datavan)
    genId: genId,
 
    getFetchQuery: (query, idField) => '',
    // calculate and return fetchKey (to determine cache hit or miss) from fetchQuery (default: defaultGetQueryString from datavan)
    getFetchKey: (query, option) => '',
 
    // another way to setup initial data. With `{ byId: {}, originals: {}, fetchAts: {} }` object tables.
    initState: {
      // byId is table of docs
      byId: {
        'user-1': { _id: 'user-1', name: 'John' },
      },
      // originals is table of modified docs' originals
      originals: {
        'user-1': { _id: 'user-1', name: 'Old Name' },
      },
      // fetchAts is server fetched queries times (msec, to prevent re-fetch after server rendering)
      fetchAts: {},
    },
  },
})
 
db.find('myUserTable', { name: 'John' })

data inside db

datavan will store docs in the following structure

const db = createDb()
db = {
  user_table: {
    submits: {
      id_1: {
        _id: 'id_1',
        name: 'John',
      },
      id_2: {
        _id: 'id_2',
        name: 'May',
      },
    },
 
    // table of modified docs' originals
    originals: {},
    preloads: {},
 
    // server fetch times/markers (msec, to prevent re-fetch after server rendering)
    fetchAts: {},
  },
}

Better Performance

memoize result and auto re-run

import { createDatavanContext, getMemoizeHoc } from 'datavan'
 
const Van = createDatavanContext(...)
const withVan = getMemoizeHoc(Van)
 
const MyApp = withVan(
  // array of props that provide to map function
  ['name', 'role'],
  // map function
  (db, { name, role }) => {
    return { users: db.find('user_table', { name, role }) }
  }
)(ReactComponent)

API

find, pick

db.find(collection, query, [option])
// Return: Array of documents
  • collection: collection name
  • query: Array | query-object (mongodb like query object, we use mingo to filter documents)
arr = db.find('user_table', { name: 'john' })
 
// query starts with $$ which trigger find within the result from onFetch response
arr = db.find('user_table', { name: 'john', $$limit: 10, $$sort: ... })
 
userById = db.pick('user_table', { name: 'john' })

findAsync, pickAsync

db.findAsync(collection, query, [option])
 
byId = db.pickAsync(collection, query, [option])

Async function that always fetch and find data from server

findInMemory

like find() but only find in local memory

fetch

internally used by find(), findAsync() to call onFetch and return a raw result in promise. Without call findInMemory() after onFetch to normalise onFetch result.

get

doc = db.get('user_table', 'id-123')

getById, getOriginals, getSubmits

// get all documents. This won't trigger onFetch()
const docsTable = db.getById('user_table')
 
// get local changed documents
const dirtyDocs = db.getSubmits('user_table')
 
// get local changed documents' originals
const originalDocs = db.getOriginals('user_table')
 
const docsPreloads = db.getPreloads('user_table')

insert

Return: inserted docs

insertedDoc = db.insert('user_table', { name: 'Mary' })
// can also insert array
insertedDocs = db.insert('user_table', [{ name: 'Mary' }, { name: 'John' }])

update

const query = { name: 'Mary' }
const mutation = { $merge: { name: 'Mary C' } }
db.update('user_table', query, mutation)

remove

remove all docs that match the query

db.remove('user_table', { name: 'May' })

mutate

mutate documents using immutability-helper syntax

// merge by doc id
db.mutate('user_table', 'id-123', { $merge: { name: 'Mary' } })
 
// merge by array of path
db.mutate('user_table', ['id-123', 'name'], { $set: 'Mary' })
 
// merge in many docs
db.mutate('user_table', { $merge: { docId1: doc1, docId2: doc2 } })

set

shortcut of mutate which always use { $set: value }

invalidate

// invalidate all collections
db.invalidate()
 
// invalidate one collection (all docs)
db.invalidate('user_table')
 
// invalidate one collection (some docs)
db.invalidate('user_table', ['user-1', 'user-2'])

reset

reset local change and re-fetch in future get/find

// reset all collections
db.reset()
 
// reset one collection (all docs)
db.reset('user_table')
 
// reset one collection (some docs)
db.reset('user_table', ['user-1', 'user-2'])

load

load bulk data into store. data can be

db.load('user_table', data, option)
  • Array of docs
  • Or a object with { byId: {}, originals: {}, fetchAts: {} }
  • Or Table of docs
// Array of docs
db.load('user_table', [{ _id: 'user-1', name: 'John' }])
 
// Or a object with at least one of `{ byId: {}, originals: {}, fetchAts: {} }`
db.load('user_table', {
  // byId is table of docs
  byId: {
    'user-1': { _id: 'user-1', name: 'John' },
  },
  // originals is table of modified docs' originals
  originals: {
    'user-1': { _id: 'user-1', name: 'Old Name' },
  },
  // fetchAts is server fetched queries times (msec, to prevent re-fetch after server rendering)
  fetchAts: {},
  // submitted tmp and stored id mapping
  $submittedIds: { tmpId: storedId },
})
 
// Or Table of docs (byId)
db.load('user_table', {
  'user-1': { _id: 'user-1', name: 'John' },
})
 
// load collections
db.load({
  'user_table': {
    'user-1': { _id: 'user-1', name: 'John' },
  },
  'collection-2': {...}
})
  • load() data will consider as fill data from backend and trigger re-render

recall

call a function (anonymous or collection-defined) only-if store data or argument changed. If no changes, cached result will be used.

// recall collection-defined function
const db = createDb({
  myUserTable: {
    groupByFunc(byId, arg1) {
      return _.groupBy(byId, arg1)
    },
  },
})
const result = db.recall('myUserTable', 'groupByFunc', 'arg1-value')

Package Sidebar

Install

npm i datavan

Weekly Downloads

1

Version

4.2.8

License

MIT

Unpacked Size

155 kB

Total Files

35

Last publish

Collaborators

  • ericfong