@evolu/common 2.0.0 → 2.0.2

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 CHANGED
@@ -1,156 +1,33 @@
1
1
  # Evolu
2
2
 
3
- [Local-first](https://www.inkandswitch.com/local-first/) platform designed for privacy, ease of use, and no vendor lock-in to sync&back precious data.
4
-
5
- - [SQLite](https://sqlite.org/) in all browsers, Electron, and React Native
6
- - E2E encrypted sync and backup with [CRDT](https://crdt.tech/) (merging changes without conflicts)
7
- - Free Evolu server for testing (paid production-ready soon, or you can run your own)
8
- - Typed database schema with branded types (`NonEmptyString1000`, `PositiveInt`, etc.)
9
- - Reactive queries with React Suspense support
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
- - Schema evolving via `filterMap` ad-hoc migration
12
- - No signup/login, no email collection, only Bitcoin-like mnemonic (12 words)
13
- - Fetching nested objects and arrays in a single SQL query
14
- - JSON support with automatic parsing and stringifying
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. Evolu stores data in the user's device(s), so Evolu apps can work offline and without a specific server. How is it different from keeping files on disk? Files are not the right abstraction for apps and are complicated to synchronize among devices. That's why client-server architecture rules the world. But as with everything, it has trade-offs.
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
- Evolu is not P2P. For reliable syncing and backup, there needs to be a server. Evolu server is very minimal, and everyone can run their own. While it's theoretically possible to have P2P Evolu, I have yet to see a reliable solution. It's not only a technical problem; it's an economic problem. Someone has to be paid to keep your data safe. Evolu provides a free server for testing. Soon we will provide a paid server for production usage.
24
+ ## Documentation
148
25
 
149
- All table columns except for ID are nullable by default. It's not a bug; it's a feature. Local-first data are meant to last forever, but schemas evolve. This design decision was inspired by GraphQL [nullability](https://graphql.org/learn/best-practices/#nullability) and [versioning](https://graphql.org/learn/best-practices/#versioning). Evolu provides a handy `filterMap` helper for queries.
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
 
@@ -32,10 +32,7 @@ export interface AppState {
32
32
  readonly reset: Effect.Effect<never, never, void>;
33
33
  }
34
34
  export declare const AppState: Context.Tag<AppState, AppState>;
35
- /**
36
- * To detect whether DOM can be used.
37
- * https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/ExecutionEnvironment.js
38
- */
35
+ /** To detect whether DOM can be used. */
39
36
  export declare const canUseDom: boolean;
40
37
  export {};
41
38
  //# sourceMappingURL=Platform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Platform.d.ts","sourceRoot":"","sources":["../../src/Platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEhD,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,eAAe,GACf,kBAAkB,GAClB,cAAc,CAAC;AAEnB,eAAO,MAAM,YAAY,yCAA8B,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAEvD,eAAO,MAAM,SAAS,mCAA2B,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAEvD,2BAA2B;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;CACrD;AAED,eAAO,MAAM,QAAQ,iCAA0B,CAAC;AAEhD,MAAM,MAAM,KAAK,GAAG,CAClB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,KACb,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,KAAK,2BAAuB,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;CAC7B;AAED,eAAO,MAAM,SAAS,kCAgBrB,CAAC;AAEF,UAAU,cAAc;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;CACnD;AAED,eAAO,MAAM,QAAQ,iCAA0B,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,SAAS,SAIrB,CAAC"}
1
+ {"version":3,"file":"Platform.d.ts","sourceRoot":"","sources":["../../src/Platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEhD,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,eAAe,GACf,kBAAkB,GAClB,cAAc,CAAC;AAEnB,eAAO,MAAM,YAAY,yCAA8B,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAEvD,eAAO,MAAM,SAAS,mCAA2B,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAEvD,2BAA2B;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;CACrD;AAED,eAAO,MAAM,QAAQ,iCAA0B,CAAC;AAEhD,MAAM,MAAM,KAAK,GAAG,CAClB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,KACb,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,KAAK,2BAAuB,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;CAC7B;AAED,eAAO,MAAM,SAAS,kCAgBrB,CAAC;AAEF,UAAU,cAAc;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;CACnD;AAED,eAAO,MAAM,QAAQ,iCAA0B,CAAC;AAEhD,yCAAyC;AACzC,eAAO,MAAM,SAAS,SAYlB,CAAC"}
@@ -15,10 +15,16 @@ export const FetchLive = Layer.succeed(Fetch, Fetch.of((url, body) => Effect.try
15
15
  catch: () => ({ _tag: "FetchError" }),
16
16
  })));
17
17
  export const AppState = Context.Tag();
18
- /**
19
- * To detect whether DOM can be used.
20
- * https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/ExecutionEnvironment.js
21
- */
22
- export const canUseDom = !!(typeof window !== "undefined" &&
23
- window.document &&
24
- window.document.createElement);
18
+ /** To detect whether DOM can be used. */
19
+ export const canUseDom = (() => {
20
+ // IDK why try-catch is necessary, but it is.
21
+ // "ReferenceError: window is not defined" should not happen, but it does.
22
+ try {
23
+ return !!(typeof window !== "undefined" &&
24
+ window.document &&
25
+ window.document.createElement);
26
+ }
27
+ catch (e) {
28
+ return false;
29
+ }
30
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
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",
package/src/Platform.ts CHANGED
@@ -70,12 +70,17 @@ export interface AppState {
70
70
 
71
71
  export const AppState = Context.Tag<AppState>();
72
72
 
73
- /**
74
- * To detect whether DOM can be used.
75
- * https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/ExecutionEnvironment.js
76
- */
77
- export const canUseDom = !!(
78
- typeof window !== "undefined" &&
79
- window.document &&
80
- window.document.createElement
81
- );
73
+ /** To detect whether DOM can be used. */
74
+ export const canUseDom = ((): boolean => {
75
+ // IDK why try-catch is necessary, but it is.
76
+ // "ReferenceError: window is not defined" should not happen, but it does.
77
+ try {
78
+ return !!(
79
+ typeof window !== "undefined" &&
80
+ window.document &&
81
+ window.document.createElement
82
+ );
83
+ } catch (e) {
84
+ return false;
85
+ }
86
+ })();