RIPPLE

Ripple is a nervous system for modern web applications

A small, isomorphic, reactive, resource-oriented, native-focused, realtime library that propagates changes across the network to all other connected servers, clients and databases.

Tweet
npm i pemrouz/ripple

Realtime Data Synchronisation

A Ripple node is just a collection of resources. You can register any JS type as a resource against a name.

If an array or object are registered, then any changes to that will be immediately propagated back to the node on the server, and subsequently to any other connected clients.

Tab 1

Tab 2

Reactive Web Components

If a function is registered, then it will be invoked on any matching Custom Elements, with the context variable as the Shadow DOM root and the arguments as any declared data.

If there are updates to the data, component or element, it will be refreshed automatically by reinvoking it. This makes all components "realtime by default", rather than afterthought.

Ripple is agnostic to how you structure your components internally (see vanilla, React and D3 examples).

Database Synchronisation

If you choose to optionally connect a (server) node to a database, it will attempt to automatically populate data resources from the database.

It will also automatically update the database if there are later any changes to the resource.

Caching & Fast Page Loads

When a resource is registered or updated, changes will be cached into localStorage.

When the page is next refreshed, your app will render instantly using resources from localStorage and then be updated when the client connects to the server and streams the latest versions. localStorage can even be faster than the browser cache.

Representations

Sometimes you may need to represent resources differently on the client than on the server. This could be to hide sensitive data, reduce large datasets for performance, or just having a more convenient structure.

By default, there is a one-to-one mapping. But you can declaratively specify a transformation that will be applied whenever resources are sent to the client, or received from the client.

The different representations will still be updated when there are changes.

Time Travel

Each data resource internally has an efficient versioned history by default. The collection of resources representing your entire application state also has a history. You can rollback individual resources, or the entire application.

This is particularly useful to achieve powerful debugging, by being able to replay the actual steps a user went through when they experience a problem. It is also useful for applications focused around colloboration.

Isomorphic Nodes - Live Example

Each Ripple node is self-contained. It can run:

  • Client-only: On a static page with no server. Useful for offline, testing and quick prototypes.

  • Server-only: Just on the server with no clients. Useful for testing and exporting bundles of resources over npm/node.

  • Both connected: Useful for real applications, where changes from one client will refresh components for all others.

  • This static page includes Ripple, and the example on the right is a live script. Try opening your console and entering the following command to see both components automatically update:

    ripple('tweets').push('hello world!')
    <script src="https://cdn.rawgit.com/pemrouz/ripple/master/dist/client.min.js"></script>
    <twitter-feed data='tweets'>
    <twitter-count data='tweets'>
    
    Read more about the rationale and how it compares with other solutions

    Feature Roadmap

    New ideas, contributions or support requests welcome.

  • Service Workers: Background Sync and Push

    Upgrade resources even when tab is not open!
  • Extended Time Travel Debugging (middleware)

    Bundle application history on error, export to server, auto-replay functionality
  • Microservices

    Make it trivial to consume resources across process/network boundaries
  • Parameterise Resources

    Enable greater range of resource representations (/tweets/latest/10 vs tweets)
  • Expose RESTful API

    Expose registered resources over HTTP endpoints, supporting multiple HATEOAS content-type flavours
  • Add Conventional Folder Shortcuts (middleware)

    For applications with many resources, auto load all resources in resources folder
  • Polyfill

    IE9+ support, polyfilling backwards using polyfill.io
  • Version History Bookmarklet

    Separate module for viewing version history & rolling back as you hover over resources.
  • Objective-C (iOS) Client

    Enable realtime changes to power native UI as well Push Notifications
  • Multiple Databases

    Provide hooks for server-database changes to allow aligning resources to different databases
  • Android Client

  • Extend ORM Database Adapters

  • Create Components Repo

  • Server-Side Rendering (middleware)

  • Cache Resources in Redis

  • Conflict Resolution: Operational Transforms

  • HTTP Cache Headers Semantics

  • API Reference

    Check out more complete examples here.

  • ripple('name')
    return the named resource, creating one if it doesn't exist
  • ripple('name', body)
    create or overwrite the named resource with the specified body
  • ripple('name', body, headers)
    create or overwrite the named resource with the specified body and extra metadata
  • ripple.resource('name', body, headers)
    alias for ripple as above that allows method chaining for registering multiple resources


  • ripple('name').on('response', function)
    react to changes on the named resource
  • ripple('name').once('response', function)
    react once to a change on the named resource


  • ripple.draw()
    redraw all components on page
  • ripple.draw(element)
    redraw specific element
  • ripple.draw.call(element)
    redraw specific element
  • ripple.draw('name')
    redraw elements that depend on resource
  • MutationObserver(ripple.draw)
    redraws element being observed


  • ripple.version('name')
    retrieves the current version index for the named resource
  • ripple.version('name', i)
    rollbacks the named resource to version i and returns it's value at that time
  • ripple.version()
    retrieves the current historical index for the entire application
  • ripple.version(i)
    rollbacks entire application state to version i


  • ripple.db('type://user:pass@host:port/dbname')
    connects the node to the database, synchronishing any changes.
  • ripple.use(ripple2)
    load the resources from another ripple node into this one
  • window.createRipple
    use this to create more nodes on client. window.ripple instantiated by default
  • require('ripple')(server, opts)
    both parameters optional. server is the http.Server instance to connect to clients on. opts:
    • client — append client library to html pages. default true
    • session — session data to make available to proxy functions


  • <component-name is="component-name" data="name(s)" template="name" css="name" inert delay="ms" realtime>
    • component-name — name of registered component resource
    • is — name of registered component resource, extends another element
    • data — name of registered data resource, multiple values separated by space
    • template — apply a template before invoking the component function
    • css — apply the css before invoking the component function
    • inert — signals to ripple to not touch this element
    • delay — time in ms to postpone rendering a component
    • realtime — this component will have frequent updates - batch updates and render once every animation frame


    Examples:
    • <time is="relative-time">
    • <fast-grid data="prices instruments" realtime>
    • <event-item data="events" id="519" template="event-item.html">


  • [header] content-type
    resource type is interpreted based on the body type - it is not necessary to explicitly set this value.
    values: application/data (default) | application/javascript | text/html | text/css.

    Example: ripple('tweets', [], { 'content-type': 'application/data' })

  • [header] content-location (alias: table)
    specify which database table/collection to populate the resource with, and sync changes with

    Example: ripple('users', [], { 'table': 'members' })

  • [header] private
    prevents this resource from every being emitted to other (client) nodes. default false.

    Example: ripple('credit-cards', [], { private: true })

  • [header] proxy-to (alias: to)
    function applied to body before being sent to clients

    Example: ripple('users', [], { to: reduceToNumber })

  • [header] proxy-from (alias: from)
    function applied to changes received from client

    Example: ripple('read-only-data', [], { from: ignoreChages })

  • [header] max-versions
    maximum number of versions to store for this resource. values: Infinity (default) | 0.

    Example: ripple('frequently-updated-data', [], { 'max-versions': 0 })

  • [header] extends
    specifies which element this component extends

    Example: ripple('color-picker', require('./picker'), { 'extends': 'input' })