@evolu/common 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -142
- package/dist/src/DbWorker.js +1 -1
- package/dist/src/Evolu.js +2 -2
- package/package.json +5 -5
- package/src/DbWorker.ts +1 -1
- package/src/Evolu.ts +5 -5
package/README.md
CHANGED
|
@@ -1,156 +1,33 @@
|
|
|
1
1
|
# Evolu
|
|
2
2
|
|
|
3
|
-
[Local-first](https://www.inkandswitch.com/local-first
|
|
4
|
-
|
|
5
|
-
- [SQLite](https://sqlite.org
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
3
|
+
[Local-first](https://www.inkandswitch.com/local-first) platform designed for privacy, ease of use, and no vendor lock-in
|
|
4
|
+
|
|
5
|
+
- [SQLite](https://sqlite.org) in all browsers, Electron, and React Native
|
|
6
|
+
- [CRDT](https://crdt.tech) for merging changes without conflicts
|
|
7
|
+
- End-to-end encrypted sync and backup
|
|
8
|
+
- Free Evolu sync and backup server, or you can run your own
|
|
9
|
+
- Typed database schema (with branded types like `NonEmptyString1000`, `PositiveInt`, etc.)
|
|
10
|
+
- Typed SQL via [Kysely](https://kysely.dev)
|
|
11
|
+
- Reactive queries with full React Suspense support
|
|
10
12
|
- Real-time experience via revalidation on focus and network recovery
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
- No signup/login, only bitcoin-like mnemonic (12 words)
|
|
14
|
+
- Ad-hoc migration
|
|
15
|
+
- Sqlite JSON support with automatic stringifying and parsing
|
|
16
|
+
- Support for [Kysely Relations](https://kysely.dev/docs/recipes/relations) (loading nested objects and arrays in a single SQL query)
|
|
17
|
+
- Local-only tables (tables with \_ prefix are not synced)
|
|
18
|
+
- Evolu Solid/Vue/Svelte soon
|
|
15
19
|
|
|
16
20
|
## Local-first apps
|
|
17
21
|
|
|
18
|
-
Local-first apps allow users to own their data
|
|
19
|
-
|
|
20
|
-
### The trade-offs of the client-server architecture
|
|
21
|
-
|
|
22
|
-
Client-server architecture provides us with easy backup and synchronization, but all that depends on the ability of a server to fulfill its promises. Internet is offline, companies go bankrupt, users are banned, and errors occur. All those things happen all the time, and then what? Right, that's why the world needs local-first apps. But until now, writing local-first apps has been challenging because of the lack of libraries and design patterns. That's why I created Evolu.
|
|
23
|
-
|
|
24
|
-
## Overview
|
|
25
|
-
|
|
26
|
-
### Define Data
|
|
27
|
-
|
|
28
|
-
To start using Evolu, define tables for your database and export React Hooks.
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
import * as S from "@effect/schema/Schema";
|
|
32
|
-
import * as Evolu from "@evolu/react";
|
|
33
|
-
|
|
34
|
-
const TodoId = Evolu.id("Todo");
|
|
35
|
-
type TodoId = S.Schema.To<typeof TodoId>;
|
|
36
|
-
|
|
37
|
-
const TodoTable = S.struct({
|
|
38
|
-
id: TodoId,
|
|
39
|
-
title: Evolu.NonEmptyString1000,
|
|
40
|
-
isCompleted: Evolu.SqliteBoolean,
|
|
41
|
-
});
|
|
42
|
-
type TodoTable = S.Schema.To<typeof TodoTable>;
|
|
43
|
-
|
|
44
|
-
const Database = S.struct({
|
|
45
|
-
todo: TodoTable,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export const {
|
|
49
|
-
useQuery,
|
|
50
|
-
useMutation,
|
|
51
|
-
useOwner,
|
|
52
|
-
useOwnerActions,
|
|
53
|
-
useEvoluError,
|
|
54
|
-
} = Evolu.create(Database);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Validate Data
|
|
58
|
-
|
|
59
|
-
Learn more about [Schema](https://github.com/effect-ts/schema).
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
import * as S from "@effect/schema/Schema";
|
|
63
|
-
import * as Evolu from "@evolu/react";
|
|
64
|
-
|
|
65
|
-
S.parse(Evolu.String1000)(title);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Mutate Data
|
|
69
|
-
|
|
70
|
-
Mutation API is designed for local-first apps to ensure changes are always merged without conflicts.
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
const { create, update } = useMutation();
|
|
74
|
-
|
|
75
|
-
create("todo", { title, isCompleted: false });
|
|
76
|
-
update("todo", { id, isCompleted: true });
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Query Data
|
|
80
|
-
|
|
81
|
-
Evolu uses type-safe TypeScript SQL query builder [kysely](https://github.com/koskimas/kysely), so autocompletion works out-of-the-box.
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
const { rows } = useQuery(
|
|
85
|
-
(db) => db.selectFrom("todo").select(["id", "title"]).orderBy("updatedAt"),
|
|
86
|
-
// (row) => row
|
|
87
|
-
({ title, ...rest }) => title && { title, ...rest },
|
|
88
|
-
);
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Protect Data
|
|
92
|
-
|
|
93
|
-
Evolu encrypts data with Mnemonic, a safe autogenerated password based on [bip39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
const owner = useOwner();
|
|
97
|
-
|
|
98
|
-
alert(owner.mnemonic);
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Delete Data
|
|
102
|
-
|
|
103
|
-
Leave no traces on a device.
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
const ownerActions = useOwnerActions();
|
|
107
|
-
|
|
108
|
-
if (confirm("Are you sure? It will delete all your local data."))
|
|
109
|
-
ownerActions.reset();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Restore Data
|
|
113
|
-
|
|
114
|
-
Restore data elsewhere. Encrypted data can only be restored with a Mnemonic.
|
|
115
|
-
|
|
116
|
-
```ts
|
|
117
|
-
const ownerActions = useOwnerActions();
|
|
118
|
-
|
|
119
|
-
ownerActions.restore(mnemonic).then((either) => {
|
|
120
|
-
if (either._tag === "Left") alert(JSON.stringify(either.left, null, 2));
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Handle Errors
|
|
125
|
-
|
|
126
|
-
Evolu `useQuery` and `useMutation` never fail, it's the advantage of local first apps, but Evolu, in rare cases, can.
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
const evoluError = useEvoluError();
|
|
130
|
-
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
// eslint-disable-next-line no-console
|
|
133
|
-
if (evoluError) console.log(evoluError);
|
|
134
|
-
}, [evoluError]);
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
And that's all. Minimal API is the key to a great developer experience.
|
|
138
|
-
|
|
139
|
-
## Privacy
|
|
140
|
-
|
|
141
|
-
Evolu uses end-to-end encryption and generates strong and safe passwords for you. Evolu sync and backup server see only userId and timestamps.
|
|
142
|
-
|
|
143
|
-
## Trade-offs
|
|
144
|
-
|
|
145
|
-
> “There are no solutions. There are only trade-offs.” ― Thomas Sowell
|
|
22
|
+
Local-first apps allow users to own their data by storing them on their devices. Modern browsers provide API designed precisely for that. How is it different from keeping files on disk? Files are not the right abstraction for apps and cannot synchronize among devices. That's why traditional apps use the client-server architecture. But using client-server architecture also means that users' ability to use an app depends on some server that can be offline, temporarily or forever, if a company decides to ban a user or even goes bankrupt. That's unfortunate. Luckily, a way to restore data ownership exists. It's Evolu.
|
|
146
23
|
|
|
147
|
-
|
|
24
|
+
## Documentation
|
|
148
25
|
|
|
149
|
-
|
|
26
|
+
For detailed information and usage examples, please visit [evolu.dev](https://www.evolu.dev).
|
|
150
27
|
|
|
151
28
|
## Community
|
|
152
29
|
|
|
153
|
-
The Evolu community is on GitHub Discussions, where you can ask questions and voice ideas.
|
|
30
|
+
The Evolu community is on [GitHub Discussions](https://github.com/evoluhq/evolu/discussions), where you can ask questions and voice ideas.
|
|
154
31
|
|
|
155
32
|
To chat with other community members, you can join the [Evolu Discord](https://discord.gg/2J8yyyyxtZ).
|
|
156
33
|
|
package/dist/src/DbWorker.js
CHANGED
|
@@ -58,7 +58,7 @@ value === undefined || (isInsert && value == null)
|
|
|
58
58
|
row: id,
|
|
59
59
|
column: key,
|
|
60
60
|
value,
|
|
61
|
-
})))),
|
|
61
|
+
})))), (a) => a.flat(), ReadonlyArray.dedupeWith(NewMessageEquivalence));
|
|
62
62
|
const ensureSchemaByNewMessages = (messages) => Effect.gen(function* (_) {
|
|
63
63
|
const tablesMap = new Map();
|
|
64
64
|
messages.forEach((message) => {
|
package/dist/src/Evolu.js
CHANGED
|
@@ -244,6 +244,6 @@ const EvoluCommon = Layer.effect(Evolu, Effect.gen(function* (_) {
|
|
|
244
244
|
tables: schemaToTables(schema),
|
|
245
245
|
}),
|
|
246
246
|
});
|
|
247
|
-
})).pipe(Layer.
|
|
247
|
+
})).pipe(Layer.provide(Layer.mergeAll(LoadQueryLive, OnQueryLive, MutateLive)), Layer.provide(LoadingPromiseLive), Layer.provide(SubscribedQueriesLive), Layer.provide(Layer.merge(RowsStoreLive, OnCompletesLive)));
|
|
248
248
|
/** EvoluCommonLive has only platform independent side-effects. */
|
|
249
|
-
export const EvoluCommonLive = EvoluCommon.pipe(Layer.
|
|
249
|
+
export const EvoluCommonLive = EvoluCommon.pipe(Layer.provide(Layer.merge(TimeLive, NanoIdLive)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/common",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Local-first platform designed for privacy, ease of use, and no vendor lock-in to sync and backup people's lifetime data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"nanoid": "^5.0.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@effect/schema": "0.
|
|
55
|
+
"@effect/schema": "0.51.1",
|
|
56
56
|
"@protobuf-ts/plugin": "^2.9.1",
|
|
57
57
|
"@protobuf-ts/protoc": "^2.9.1",
|
|
58
58
|
"array-shuffle": "^3.0.0",
|
|
59
59
|
"better-sqlite3": "^9.1.1",
|
|
60
|
-
"effect": "2.0.0-next.
|
|
60
|
+
"effect": "2.0.0-next.58",
|
|
61
61
|
"eslint": "^8.54.0",
|
|
62
62
|
"typescript": "^5.3.2",
|
|
63
63
|
"vitest": "^0.34.6",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"eslint-config-evolu": "1.0.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@effect/schema": "0.
|
|
69
|
-
"effect": "2.0.0-next.
|
|
68
|
+
"@effect/schema": "0.51.1",
|
|
69
|
+
"effect": "2.0.0-next.58"
|
|
70
70
|
},
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|
package/src/DbWorker.ts
CHANGED
package/src/Evolu.ts
CHANGED
|
@@ -755,13 +755,13 @@ const EvoluCommon = Layer.effect(
|
|
|
755
755
|
});
|
|
756
756
|
}),
|
|
757
757
|
).pipe(
|
|
758
|
-
Layer.
|
|
759
|
-
Layer.
|
|
760
|
-
Layer.
|
|
761
|
-
Layer.
|
|
758
|
+
Layer.provide(Layer.mergeAll(LoadQueryLive, OnQueryLive, MutateLive)),
|
|
759
|
+
Layer.provide(LoadingPromiseLive),
|
|
760
|
+
Layer.provide(SubscribedQueriesLive),
|
|
761
|
+
Layer.provide(Layer.merge(RowsStoreLive, OnCompletesLive)),
|
|
762
762
|
);
|
|
763
763
|
|
|
764
764
|
/** EvoluCommonLive has only platform independent side-effects. */
|
|
765
765
|
export const EvoluCommonLive = EvoluCommon.pipe(
|
|
766
|
-
Layer.
|
|
766
|
+
Layer.provide(Layer.merge(TimeLive, NanoIdLive)),
|
|
767
767
|
);
|