Skip to content

Commit

Permalink
Document local-only tables and deferred sync pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
steida committed Dec 5, 2023
1 parent d36f7fa commit a258841
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 3 deletions.
1 change: 1 addition & 0 deletions apps/web/pages/docs/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"installation": "Installation",
"quickstart": "Quickstart",
"api": "API",
"patterns": "Patterns",
"evolu-server": "Evolu Server",
"how-evolu-works": "How Evolu Works",
"comparison": "Comparison",
Expand Down
42 changes: 42 additions & 0 deletions apps/web/pages/docs/patterns.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Callout } from "nextra-theme-docs";

# Patterns

## Deferred Sync with Local-Only Tables

Tables with a name prefixed with `_` are local only, which means they are never
synced. It's useful for device-specific or temporal data.

Imagine editing a JSON representing a rich-text formatted document. Syncing the
whole document on every change would be inefficient. The ideal solution could
be to use some advanced CRDT logic, for example, the
[Peritext](https://www.inkandswitch.com/peritext), but a reliable implementation
doesn't exist yet.

Fortunately, we can leverage Evolu's local-only tables instead. Saving huge
JSON on every keystroke isn't an issue because Evolu uses Web Workers,
so saving doesn't block the main thread. In React Native, we use
`InteractionManager.runAfterInteractions` (soon).

<Callout>
Is postMessage slow? No, not really. (It depends.)
[surma.dev/things/is-postmessage-slow](https://surma.dev/things/is-postmessage-slow)
</Callout>

When we decide it's time to sync, we move data from the local-only table to the
regular table. There is no API for that; just set `isDeleted` to `true` and insert
data into a new table. Evolu batches mutations in microtask and runs it within a
transaction, so there is no chance for data loss.

```ts
// Both `update` and `create` run within a transaction.
evolu.update("_todo", { id: someId, isDeleted: true });
// This mutation starts syncing immediately.
evolu.create("todo", { title });
```

The last question is, when should we do that? We can expose an explicit sync
button, but that's not a friendly UX. The better approach is to use a reliable
heuristic to detect the user unit of work. We can leverage page visibility,
a route change, and other techniques. Unfortunately, we can't rely on unload
event because it's unreliable. **Evolu will release a helper for that soon.**
7 changes: 6 additions & 1 deletion packages/evolu-common-web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
} from "./PlatformLive.js";

/**
* Create Evolu for web.
* Create Evolu for the web.
*
* Tables with a name prefixed with `_` are local only, which means they are
* never synced. It's useful for device-specific or temporal data.
*
* @example
* import * as S from "@effect/schema/Schema";
Expand All @@ -31,6 +34,8 @@ import {
* type TodoTable = S.Schema.To<typeof TodoTable>;
*
* const Database = S.struct({
* // _todo is local only table
* _todo: TodoTable,
* todo: TodoTable,
* });
* type Database = S.Schema.To<typeof Database>;
Expand Down
12 changes: 10 additions & 2 deletions packages/evolu-react-native/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ const EvoluNativeLive: Layer.Layer<
);

/**
* Create Evolu for web.
* Create Evolu for React Native.
*
* Tables with a name prefixed with `_` are local only, which means they are
* never synced. It's useful for device-specific or temporal data.
*
* @example
* import * as S from "@effect/schema/Schema";
* import { NonEmptyString1000, createEvolu, id } from "@evolu/react";
* import {
* NonEmptyString1000,
* createEvolu,
* id,
* } from "@evolu/react-native";
*
* const TodoId = id("Todo");
* type TodoId = S.Schema.To<typeof TodoId>;
Expand All @@ -83,6 +90,7 @@ const EvoluNativeLive: Layer.Layer<
* type TodoTable = S.Schema.To<typeof TodoTable>;
*
* const Database = S.struct({
* // _todo is local only table
* todo: TodoTable,
* });
* type Database = S.Schema.To<typeof Database>;
Expand Down

1 comment on commit a258841

@vercel
Copy link

@vercel vercel bot commented on a258841 Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

evolu – ./

evolu-evolu.vercel.app
www.evolu.dev
evolu-git-main-evolu.vercel.app
evolu.vercel.app
evolu.dev

Please sign in to comment.