🔂 Send patches around to keep the system in sync.

Overview

The core idea is to use patches to keep the UI in sync between client and server, multiple clients, or multiple windows.

It uses Immer as an interface for state mutations and provides a convenient way to group mutations into a single transaction, and enables undo/redo out of the box.

Play with it on Codesandbox

Read the article

Features

  1. Sync application state using patches
  2. Get undo/redo for free
  3. Sync to the server
  4. Server agnostic
  5. State management libraries agnostic (a container interface)
  6. Small bundle size
  7. Sync between iframes (not implemented yet)
  8. Sync between tabs (not implemented yet)
  9. Resolve conflicts (not implemented yet)
  10. Provide server handler (not implemented yet)

Example

import store, { sync } from "immerhin";

// Create containers for each state. Sync engine only cares that the result has a "value" and a "dispatch(newValue)"
const container1 = createContainer(initialValue);
const container2 = createContainer(initialValue);

// - Explicitely enable containers for transactions
// - Define a namespace for each container, so that server knows which object it has to patch.
store.register("container1", container1);
store.register("container2", container2);

// Creating the actual transaction that will:
// - generate patches
// - update states
// - inform all subscribers
// - register a transaction for potential undo/redo and sync calls
store.createTransaction(
  [container1, container2, ...rest],
  (value1, value2, ...rest) => {
    mutateValue(value1);
    mutateValue(value2);
    // ...
  }
);

// Setup periodic sync with a fetch, or do this with Websocket
setInterval(async () => {
  const entries = sync();
  await fetch("/patch", { method: "POST", payload: JSON.stringify(entries) });
}, 1000);

// Undo/redo

store.undo();
store.redo();

How it works

Containers

A container is an interface that provides a .value and implements a .dispatch(value) method so that a value can be updated and propagated to all consumers.

You can use anything to create containers, it could be a Redux store, could be an observable or a nano state

You can use the same container instance to subscribe to the changes across the entire application.

Example using nano state:

import { createContainer, useValue } from "react-nano-state";
const myContainer = createContainer(initialValue);

// I can call a dispatch from anywhere
myContainer.dispatch(newValue);

// I can subscribe to updates in React
const Component = () => {
  const [value, setValue] = useValue(myContainer);
};

Container registration

We register containers for two reasons:

  1. To define a namespace for each container so that whoever consumes the changes knows which object to apply the patches to.
  2. Ensure that the container was intentionally registered to be synced to the server and be part of undo/redo transactions. You may not want this for every container since you can use them for ephemeral states.

Example

store.register("myName", myContainer);

Creating a transaction

A transaction is a set of changes applied to a set of states. When you apply changes to the states inside a transaction, you are essentially telling the engine which changes are associated with the same user action so that undo/redo can use that as a single step to work with.

A call into store.createTransaction()does all of this:

  • generate patches (using Immer)
  • update states and inform all subscribers (by calling container.dispatch(newValue))
  • register a transaction for potential undo/redo and calls

Example

store.createTransaction(
  [container1, container2, ...rest],
  (value1, value2, ...rest) => {
    mutateValue(value1);
    mutateValue(value2);
    // ...
  }
);

Undo/redo

Calling undo() and redo() functions will essentially apply the right patch for the value and dispatch the update.

Sync

The sync() function returns you all changes queued up for a sync since the last call. With the return from sync(), you can do anything you want, for example, send it to your server.

Example

// Setup periodic sync with a fetch, or do this with Websocket
setInterval(async () => {
  const entries = sync();
  await fetch("/patch", { method: "POST", payload: JSON.stringify(entries) });
}, 1000);

Example entries:

[
  {
    "transactionId": "6243062b469f516835327f65",
    "changes": [
      {
        "namespace": "root",
        "patches": [
          {
            "op": "replace",
            "path": ["children", 1],
            "value": {
              "component": "Box",
              "id": "6241f55791596f2467df9c2a",
              "style": {},
              "children": []
            }
          },
          {
            "op": "replace",
            "path": ["children", 2],
            "value": {
              "component": "Box",
              "id": "6241f55a91596f2467df9c36",
              "style": {},
              "children": []
            }
          },
          {
            "op": "replace",
            "path": ["children", "length"],
            "value": 3
          }
        ]
      }
    ]
  }
]

Create a new store

If you want to have multiple separate undoable states, create a separate store for each. They add to the same sync queue in the end.

import { Store } from "immerhin";

const store = new Store();
You might also like...

Sync pages from Notion to GitHub to be used as a static website (JAM)

Sync pages from Notion to GitHub to be used as a static website (JAM)

notion-jam Sync pages from Notion to GitHub to be used as a static website (JAM) Usage Quick Start Create a new Notion Integration Add Notion secret t

Nov 15, 2022

A CLI tool to make Taobao's npm mirror sync your package immediately.

npm-mirror-sync A CLI tool to make Taobao's npm mirror sync your package immediately. 让淘宝的 NPM 镜像立即收录你的包的新版本。 背景 相信国内小伙伴都在用淘宝的 NPM 镜像(npmmirror.com)作为

Jun 9, 2022

Obsidan Plugin: Simpread annote sync

描述 SimpRead.Unreader.Sync.mp4 安装此插件后,在简悦中加入稍后读 / 标注(包括:新增或更新)均可实时同步到 Obsidian 库相应的文件夹中。(每个稍后读(标注)对应一个 Markdown 文件) 意义 通常在 Web 标注的话,大多需要这样的操作步骤:标注后 → 复

Dec 28, 2022

🍎 A simple application which enables you to sync readings from your Withings scale to a Notion page.

🍎 A simple application which enables you to sync readings from your Withings scale to a Notion page.

weight-logger weight-logger is a simple application which enables you to sync readings from your Withings scale to a Notion page. Preview Installation

Jan 14, 2022

Lip-sync VRM avatar client for zero-webcam mic-based vtubing

Lip-sync VRM avatar client for zero-webcam mic-based vtubing

VU-VRM A lip-sync VRM avatar client for zero-webcam mic-based vtubing: automattic.github.io/VU-VRM/ Why? Because multitasking. Because sometimes you n

Oct 19, 2022

Markdown note maker (with Git sync) using Tauri.

Markdown note maker (with Git sync) using Tauri.

Mediocre Markdown note maker (with Git sync) using Tauri. Screens Tech Stack Frontend Monaco Editor for the editor interface Chakra UI library Redux T

Dec 6, 2022

Recoil Sync stores for Next.js

recoil-sync-next Recoil Sync stores for Next.js Features URL Persistence Syncing an atom with the browser URL. Session Storage Persistence Synced with

Dec 28, 2022

Tries to execute sync/async function, returns a specified default value if the function throws

good-try Tries to execute sync/async function, returns a specified default value if the function throws. Why Why not nice-try with it's 70+ million do

Dec 8, 2022

A modern ebook manager and reader with sync and backup capacities for Windows, macOS, Linux and Web

A modern ebook manager and reader with sync and backup capacities for Windows, macOS, Linux and Web

简体中文 | English Koodo Reader A cross-platform ebook reader Download | Preview | Roadmap | Document Preview Feature Format support: EPUB (.epub) Scanned

Dec 29, 2022
Owner
Webstudio
Webstudio is an Open Source Visual Development Platform for developers, designers, and cross-functional teams.
Webstudio
This experimental library patches the global custom elements registry to allow re-defining or reload a custom element.

Redefine Custom Elements This experimental library patches the global custom elements registry to allow re-defining a custom element. Based on the spe

Caridy Patiño 21 Dec 11, 2022
Patches the AssemblyScript compiler to utilize WASI imports instead of Web APIs.

WASI shim for AssemblyScript Patches the AssemblyScript compiler to utilize WASI imports instead of Web APIs. Note that this shim also serves a higher

The AssemblyScript Project 37 Dec 23, 2022
The Frontend of Escobar's Inventory Management System, Employee Management System, Ordering System, and Income & Expense System

Usage Create an App # with npx $ npx create-nextron-app my-app --example with-javascript # with yarn $ yarn create nextron-app my-app --example with-

Viver Bungag 4 Jan 2, 2023
Evolve is an online investment portfolio management system where users can keep track of all the assets that they have invested in and how well their assets are performing.

Evolve is an online investment portfolio management system where users can keep track of all the assets that they have invested in and how well their assets are performing.

Indrajit 6 Oct 16, 2022
Automatically sync your leetcode solutions to your github account - top 5 trending GitHub repository

LeetHub - Automatically sync your code to GitHub. Top 5 Trending JavaScript Repositories Available on: What is LeetHub? A chrome extension that automa

Qasim Wani 2.8k Dec 31, 2022
This tool allows you to test your chains.json file to see if your chains are available, syncing, or in sync.

Chains Tester This tool allows you to test your chains.json file to see if your chains are available, syncing, or in sync. This is an open source tool

Jorge S. Cuesta 9 Nov 4, 2022
Obsidian plugin to sync Pinboard.in links to Daily Notes

Obsidian Pinboard Sync An Obsidian plugin that adds links you've saved with Pinboard to your Obsidian Daily Notes, synchronizing periodically. Why? I'

Mathew Spolin 35 Dec 1, 2022
Obsidian.md plugin to sync highlights/notes from koreader

Obsidian KOReader Plugin Sync KOReader notes in your Obsidian vault. The KOReader device must be connected to the device running obsidian to let the p

Federico Granata 24 Dec 18, 2022
Get a diff view of your Obsidian Sync, File Recovery and Git version history

Version History Diff (for Sync and File Recovery Core plugins and Git) Note This plugin uses private APIs, so it may break at any time. Use at your ow

null 39 Dec 26, 2022
Sync your personal calendar to your work calendar, privately 🐒

Callibella ?? It is considered unusual among Callibella in that it gives birth to only a single baby instead of twins, the norm for Callibella. Wikiep

Yo'av Moshe 19 Oct 12, 2022