diff --git a/apps/web/pages/docs/_meta.json b/apps/web/pages/docs/_meta.json index 4da8a2d54..9430b06b3 100644 --- a/apps/web/pages/docs/_meta.json +++ b/apps/web/pages/docs/_meta.json @@ -3,6 +3,7 @@ "installation": "Installation", "quickstart": "Quickstart", "api": "API", + "patterns": "Patterns", "evolu-server": "Evolu Server", "how-evolu-works": "How Evolu Works", "comparison": "Comparison", diff --git a/apps/web/pages/docs/patterns.mdx b/apps/web/pages/docs/patterns.mdx new file mode 100644 index 000000000..02d807392 --- /dev/null +++ b/apps/web/pages/docs/patterns.mdx @@ -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). + + + Is postMessage slow? No, not really. (It depends.) + [surma.dev/things/is-postmessage-slow](https://surma.dev/things/is-postmessage-slow) + + +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.** diff --git a/packages/evolu-common-web/src/index.ts b/packages/evolu-common-web/src/index.ts index 35857a434..873102099 100644 --- a/packages/evolu-common-web/src/index.ts +++ b/packages/evolu-common-web/src/index.ts @@ -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"; @@ -31,6 +34,8 @@ import { * type TodoTable = S.Schema.To; * * const Database = S.struct({ + * // _todo is local only table + * _todo: TodoTable, * todo: TodoTable, * }); * type Database = S.Schema.To; diff --git a/packages/evolu-react-native/src/index.ts b/packages/evolu-react-native/src/index.ts index 9a7780e0a..937244fff 100644 --- a/packages/evolu-react-native/src/index.ts +++ b/packages/evolu-react-native/src/index.ts @@ -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; @@ -83,6 +90,7 @@ const EvoluNativeLive: Layer.Layer< * type TodoTable = S.Schema.To; * * const Database = S.struct({ + * // _todo is local only table * todo: TodoTable, * }); * type Database = S.Schema.To;