@evolu/common 1.0.0

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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/src/Config.d.ts +20 -0
  4. package/dist/src/Config.d.ts.map +1 -0
  5. package/dist/src/Config.js +8 -0
  6. package/dist/src/Crypto.d.ts +42 -0
  7. package/dist/src/Crypto.d.ts.map +1 -0
  8. package/dist/src/Crypto.js +44 -0
  9. package/dist/src/Db.d.ts +60 -0
  10. package/dist/src/Db.d.ts.map +1 -0
  11. package/dist/src/Db.js +118 -0
  12. package/dist/src/DbWorker.d.ts +78 -0
  13. package/dist/src/DbWorker.d.ts.map +1 -0
  14. package/dist/src/DbWorker.js +282 -0
  15. package/dist/src/Diff.d.ts +19 -0
  16. package/dist/src/Diff.d.ts.map +1 -0
  17. package/dist/src/Diff.js +86 -0
  18. package/dist/src/Errors.d.ts +19 -0
  19. package/dist/src/Errors.d.ts.map +1 -0
  20. package/dist/src/Errors.js +11 -0
  21. package/dist/src/Evolu.d.ts +71 -0
  22. package/dist/src/Evolu.d.ts.map +1 -0
  23. package/dist/src/Evolu.js +235 -0
  24. package/dist/src/MerkleTree.d.ts +15 -0
  25. package/dist/src/MerkleTree.d.ts.map +1 -0
  26. package/dist/src/MerkleTree.js +49 -0
  27. package/dist/src/Model.d.ts +143 -0
  28. package/dist/src/Model.d.ts.map +1 -0
  29. package/dist/src/Model.js +103 -0
  30. package/dist/src/Murmurhash.d.ts +2 -0
  31. package/dist/src/Murmurhash.d.ts.map +1 -0
  32. package/dist/src/Murmurhash.js +60 -0
  33. package/dist/src/Platform.d.ts +29 -0
  34. package/dist/src/Platform.d.ts.map +1 -0
  35. package/dist/src/Platform.js +17 -0
  36. package/dist/src/Protobuf.d.ts +130 -0
  37. package/dist/src/Protobuf.d.ts.map +1 -0
  38. package/dist/src/Protobuf.js +100 -0
  39. package/dist/src/Public.d.ts +7 -0
  40. package/dist/src/Public.d.ts.map +1 -0
  41. package/dist/src/Public.js +2 -0
  42. package/dist/src/Sql.d.ts +12 -0
  43. package/dist/src/Sql.d.ts.map +1 -0
  44. package/dist/src/Sql.js +100 -0
  45. package/dist/src/Sqlite.d.ts +30 -0
  46. package/dist/src/Sqlite.d.ts.map +1 -0
  47. package/dist/src/Sqlite.js +41 -0
  48. package/dist/src/Store.d.ts +9 -0
  49. package/dist/src/Store.d.ts.map +1 -0
  50. package/dist/src/Store.js +18 -0
  51. package/dist/src/SyncWorker.d.ts +73 -0
  52. package/dist/src/SyncWorker.d.ts.map +1 -0
  53. package/dist/src/SyncWorker.js +136 -0
  54. package/dist/src/Timestamp.d.ts +44 -0
  55. package/dist/src/Timestamp.d.ts.map +1 -0
  56. package/dist/src/Timestamp.js +76 -0
  57. package/dist/src/index.d.ts +19 -0
  58. package/dist/src/index.d.ts.map +1 -0
  59. package/dist/src/index.js +18 -0
  60. package/package.json +76 -0
  61. package/src/Config.ts +33 -0
  62. package/src/Crypto.ts +113 -0
  63. package/src/Db.ts +323 -0
  64. package/src/DbWorker.ts +683 -0
  65. package/src/Diff.ts +113 -0
  66. package/src/Errors.ts +33 -0
  67. package/src/Evolu.ts +527 -0
  68. package/src/MerkleTree.ts +83 -0
  69. package/src/Model.ts +212 -0
  70. package/src/Murmurhash.ts +70 -0
  71. package/src/Platform.ts +63 -0
  72. package/src/Protobuf.ts +204 -0
  73. package/src/Public.ts +6 -0
  74. package/src/Sql.ts +114 -0
  75. package/src/Sqlite.ts +92 -0
  76. package/src/Store.ts +32 -0
  77. package/src/SyncWorker.ts +344 -0
  78. package/src/Timestamp.ts +192 -0
  79. package/src/index.ts +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Evolu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # Evolu
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
10
+ - 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
15
+
16
+ ## Local-first apps
17
+
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 Schema from "@effect/schema/Schema";
32
+ import * as Evolu from "@evolu/react";
33
+
34
+ const TodoId = Evolu.id("Todo");
35
+ type TodoId = Schema.Schema.To<typeof TodoId>;
36
+
37
+ const TodoTable = Schema.struct({
38
+ id: TodoId,
39
+ title: Evolu.NonEmptyString1000,
40
+ isCompleted: Evolu.SqliteBoolean,
41
+ });
42
+ type TodoTable = Schema.Schema.To<typeof TodoTable>;
43
+
44
+ const Database = Schema.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 Schema from "@effect/schema/Schema";
63
+ import * as Evolu from "@evolu/react";
64
+
65
+ Schema.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
146
+
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.
148
+
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.
150
+
151
+ ## Community
152
+
153
+ The Evolu community is on GitHub Discussions, where you can ask questions and voice ideas.
154
+
155
+ To chat with other community members, you can join the [Evolu Discord](https://discord.gg/2J8yyyyxtZ).
156
+
157
+ [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/evoluhq.svg?style=social&label=Follow%20%40evoluhq)](https://twitter.com/evoluhq)
158
+
159
+ ## FAQ
160
+
161
+ ### Is Evolu ready for production?
162
+
163
+ Yes. Evolu is stable.
164
+
165
+ ### What is the SQLite database size limit?
166
+
167
+ [Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)
168
+
169
+ ### How can I check the current database filesize?
170
+
171
+ Use OPFS Explorer Chrome DevTools extension.
172
+
173
+ ## Contributing
174
+
175
+ Evolu monorepo uses [pnpm](https://pnpm.io).
176
+
177
+ Install the dependencies with:
178
+
179
+ ```
180
+ pnpm install
181
+ ```
182
+
183
+ Build Evolu monorepo:
184
+
185
+ ```
186
+ pnpm build
187
+ ```
188
+
189
+ Start developing and watch for code changes:
190
+
191
+ ```
192
+ pnpm dev
193
+ ```
194
+
195
+ Lint and tests:
196
+
197
+ ```
198
+ pnpm lint test
199
+ ```
200
+
201
+ Describe changes for release log:
202
+
203
+ ```
204
+ pnpm changeset
205
+ ```
@@ -0,0 +1,20 @@
1
+ import { Context, Layer } from "effect";
2
+ export interface Config {
3
+ /**
4
+ * Alternate URL to Evolu sync&backup server.
5
+ */
6
+ readonly syncUrl: string;
7
+ /**
8
+ * Alternate URL to reload browser tabs after `Owner` reset or restore.
9
+ * The default value is `/`.
10
+ */
11
+ readonly reloadUrl: string;
12
+ /**
13
+ * Maximum physical clock drift allowed in ms.
14
+ * The default value is 5 * 60 * 1000 (5 minutes).
15
+ */
16
+ readonly maxDrift: number;
17
+ }
18
+ export declare const Config: Context.Tag<Config, Config>;
19
+ export declare const ConfigLive: (config?: Partial<Config>) => Layer.Layer<never, never, Config>;
20
+ //# sourceMappingURL=Config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/Config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,WAAW,MAAM;IACrB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,MAAM,6BAAsC,CAAC;AAE1D,eAAO,MAAM,UAAU,YACZ,QAAQ,MAAM,CAAC,KACvB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAShC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Context, Layer } from "effect";
2
+ export const Config = Context.Tag("evolu/Config");
3
+ export const ConfigLive = (config) => Layer.succeed(Config, Config.of({
4
+ syncUrl: "https://evolu.world",
5
+ maxDrift: 5 * 60 * 1000,
6
+ reloadUrl: "/",
7
+ ...config,
8
+ }));
@@ -0,0 +1,42 @@
1
+ import * as Schema from "@effect/schema/Schema";
2
+ import { Brand, Context, Effect, Layer } from "effect";
3
+ export interface Bip39 {
4
+ readonly make: Effect.Effect<never, never, Mnemonic>;
5
+ readonly toSeed: (mnemonic: Mnemonic) => Effect.Effect<never, never, Uint8Array>;
6
+ readonly parse: (mnemonic: string) => Effect.Effect<never, InvalidMnemonicError, Mnemonic>;
7
+ }
8
+ export declare const Bip39: Context.Tag<Bip39, Bip39>;
9
+ export interface InvalidMnemonicError {
10
+ readonly _tag: "InvalidMnemonicError";
11
+ }
12
+ /**
13
+ * Mnemonic is a password generated by Evolu in BIP39 format.
14
+ *
15
+ * A mnemonic, also known as a "seed phrase," is a set of 12 words in a
16
+ * specific order chosen from a predefined list. The purpose of the BIP39
17
+ * mnemonic is to provide a human-readable way of storing a private key.
18
+ */
19
+ export type Mnemonic = string & Brand.Brand<"Mnemonic">;
20
+ export interface NanoId {
21
+ readonly nanoid: Effect.Effect<never, never, string>;
22
+ readonly nanoidAsNodeId: Effect.Effect<never, never, NodeId>;
23
+ }
24
+ export declare const NanoId: Context.Tag<NanoId, NanoId>;
25
+ export declare const NodeId: Schema.BrandSchema<string, string & Brand.Brand<"NodeId">>;
26
+ export type NodeId = Schema.Schema.To<typeof NodeId>;
27
+ export declare const NanoIdLive: Layer.Layer<never, never, NanoId>;
28
+ /**
29
+ * SLIP-21 implementation
30
+ * https://github.com/satoshilabs/slips/blob/master/slip-0021.md
31
+ */
32
+ export declare const slip21Derive: (seed: Uint8Array, path: string[]) => Effect.Effect<never, never, Uint8Array>;
33
+ /**
34
+ * Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
35
+ */
36
+ export interface SecretBox {
37
+ readonly seal: (key: Uint8Array, plaintext: Uint8Array) => Effect.Effect<never, never, Uint8Array>;
38
+ readonly open: (key: Uint8Array, ciphertext: Uint8Array) => Effect.Effect<never, never, Uint8Array>;
39
+ }
40
+ export declare const SecretBox: Context.Tag<SecretBox, SecretBox>;
41
+ export declare const SecretBoxLive: Layer.Layer<never, never, SecretBox>;
42
+ //# sourceMappingURL=Crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Crypto.d.ts","sourceRoot":"","sources":["../../src/Crypto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAMhD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGvD,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAErD,QAAQ,CAAC,MAAM,EAAE,CACf,QAAQ,EAAE,QAAQ,KACf,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAE7C,QAAQ,CAAC,KAAK,EAAE,CACd,QAAQ,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,oBAAoB,EAAE,QAAQ,CAAC,CAAC;CAC3D;AAED,eAAO,MAAM,KAAK,2BAAoC,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;CACvC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AAExD,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;CAC9D;AAED,eAAO,MAAM,MAAM,6BAAsC,CAAC;AAE1D,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,WAAW,CACrC,MAAM,EACN,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAC6C,CAAC;AAC9E,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC;AAIrD,eAAO,MAAM,UAAU,mCAMtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,SACjB,UAAU,QACV,MAAM,EAAE,KACb,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAWrC,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,CACb,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,UAAU,KAClB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAE7C,QAAQ,CAAC,IAAI,EAAE,CACb,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,UAAU,KACnB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;CAC9C;AAED,eAAO,MAAM,SAAS,mCAA4C,CAAC;AAEnE,eAAO,MAAM,aAAa,sCAgBzB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import * as Schema from "@effect/schema/Schema";
2
+ import { secretbox } from "@noble/ciphers/salsa";
3
+ import { concatBytes } from "@noble/ciphers/utils";
4
+ import { hmac } from "@noble/hashes/hmac";
5
+ import { sha512 } from "@noble/hashes/sha512";
6
+ import { randomBytes } from "@noble/hashes/utils";
7
+ import { Context, Effect, Layer } from "effect";
8
+ import { customAlphabet, nanoid } from "nanoid";
9
+ export const Bip39 = Context.Tag("evolu/Bip39");
10
+ export const NanoId = Context.Tag("evolu/NanoId");
11
+ export const NodeId = Schema.string.pipe(Schema.pattern(/^[\w-]{16}$/), Schema.brand("NodeId"));
12
+ const nanoidForNodeId = customAlphabet("0123456789abcdef", 16);
13
+ export const NanoIdLive = Layer.succeed(NanoId, NanoId.of({
14
+ nanoid: Effect.sync(() => nanoid()),
15
+ nanoidAsNodeId: Effect.sync(() => nanoidForNodeId()),
16
+ }));
17
+ /**
18
+ * SLIP-21 implementation
19
+ * https://github.com/satoshilabs/slips/blob/master/slip-0021.md
20
+ */
21
+ export const slip21Derive = (seed, path) => Effect.sync(() => {
22
+ let m = hmac(sha512, "Symmetric key seed", seed);
23
+ for (let i = 0; i < path.length; i++) {
24
+ const p = new TextEncoder().encode(path[i]);
25
+ const e = new Uint8Array(p.byteLength + 1);
26
+ e[0] = 0;
27
+ e.set(p, 1);
28
+ m = hmac(sha512, m.slice(0, 32), e);
29
+ }
30
+ return m.slice(32, 64);
31
+ });
32
+ export const SecretBox = Context.Tag("evolu/SecretBox");
33
+ export const SecretBoxLive = Layer.succeed(SecretBox, SecretBox.of({
34
+ seal: (key, plaintext) => Effect.sync(() => {
35
+ const nonce = randomBytes(24);
36
+ const ciphertext = secretbox(key, nonce).seal(plaintext);
37
+ return concatBytes(nonce, ciphertext);
38
+ }),
39
+ open: (key, ciphertext) => Effect.sync(() => {
40
+ const nonce = ciphertext.subarray(0, 24);
41
+ const ciphertextWithoutNonce = ciphertext.subarray(24);
42
+ return secretbox(key, nonce).open(ciphertextWithoutNonce);
43
+ }),
44
+ }));
@@ -0,0 +1,60 @@
1
+ import * as S from "@effect/schema/Schema";
2
+ import { Brand, Context, Effect, ReadonlyRecord } from "effect";
3
+ import * as Kysely from "kysely";
4
+ import { Bip39, Mnemonic, NanoId } from "./Crypto.js";
5
+ import { Id, SqliteBoolean, SqliteDate } from "./Model.js";
6
+ import { Query, Row, Sqlite, Value } from "./Sqlite.js";
7
+ export type TableSchema = ReadonlyRecord.ReadonlyRecord<Value> & {
8
+ readonly id: Id;
9
+ };
10
+ export type Schema = ReadonlyRecord.ReadonlyRecord<TableSchema>;
11
+ export type CreateQuery<S extends Schema> = (queryCallback: QueryCallback<S, Row>) => Query;
12
+ export type QueryCallback<S extends Schema, QueryRow> = (db: KyselyWithoutMutation<SchemaForQuery<S>>) => Kysely.SelectQueryBuilder<any, any, QueryRow>;
13
+ type KyselyWithoutMutation<DB> = Pick<Kysely.Kysely<DB>, "selectFrom" | "fn">;
14
+ type SchemaForQuery<S extends Schema> = {
15
+ readonly [Table in keyof S]: NullableExceptOfId<{
16
+ readonly [Column in keyof S[Table]]: S[Table][Column];
17
+ } & CommonColumns>;
18
+ };
19
+ export type NullableExceptOfId<T> = {
20
+ readonly [K in keyof T]: K extends "id" ? T[K] : T[K] | null;
21
+ };
22
+ export interface CommonColumns {
23
+ readonly createdAt: SqliteDate;
24
+ readonly updatedAt: SqliteDate;
25
+ readonly isDeleted: SqliteBoolean;
26
+ }
27
+ export interface Table {
28
+ readonly name: string;
29
+ readonly columns: ReadonlyArray<string>;
30
+ }
31
+ export type Tables = ReadonlyArray<Table>;
32
+ export declare const makeCreateQuery: <T extends Schema>() => CreateQuery<T>;
33
+ export declare const schemaToTables: (schema: S.Schema<any, any>) => Tables;
34
+ /**
35
+ * `Owner` represents the Evolu database owner. Evolu auto-generates `Owner`
36
+ * on the first run. `Owner` can be reset on the current device and restored
37
+ * on a different one.
38
+ */
39
+ export interface Owner {
40
+ /** The `Mnemonic` associated with `Owner`. */
41
+ readonly mnemonic: Mnemonic;
42
+ /** The unique identifier of `Owner` safely derived from its `Mnemonic`. */
43
+ readonly id: OwnerId;
44
+ readonly encryptionKey: Uint8Array;
45
+ }
46
+ export declare const Owner: Context.Tag<Owner, Owner>;
47
+ /**
48
+ * The unique identifier of `Owner` safely derived from its `Mnemonic`.
49
+ */
50
+ export type OwnerId = Id & Brand.Brand<"Owner">;
51
+ export declare const transaction: <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<Sqlite | R, E, A>;
52
+ export interface NoSuchTableOrColumnError {
53
+ readonly _tag: "NoSuchTableOrColumnError";
54
+ }
55
+ export declare const someDefectToNoSuchTableOrColumnError: <R, E, A>(self: Effect.Effect<R, E, A>) => Effect.Effect<R, NoSuchTableOrColumnError | E, A>;
56
+ export declare const makeOwner: (mnemonic?: Mnemonic) => Effect.Effect<Bip39, never, Owner>;
57
+ export declare const lazyInit: (mnemonic?: Mnemonic) => Effect.Effect<Sqlite | Bip39 | NanoId, never, Owner>;
58
+ export declare const ensureSchema: (tables: Tables) => Effect.Effect<Sqlite, never, void>;
59
+ export {};
60
+ //# sourceMappingURL=Db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"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"}
package/dist/src/Db.js ADDED
@@ -0,0 +1,118 @@
1
+ import * as AST from "@effect/schema/AST";
2
+ import { make } from "@effect/schema/Schema";
3
+ import { bytesToHex } from "@noble/ciphers/utils";
4
+ import { Context, Effect, Exit, Option, Predicate, ReadonlyArray, ReadonlyRecord, String, pipe, } from "effect";
5
+ import * as Kysely from "kysely";
6
+ import { urlAlphabet } from "nanoid";
7
+ import { Bip39, slip21Derive } from "./Crypto.js";
8
+ import { initialMerkleTree, merkleTreeToString } from "./MerkleTree.js";
9
+ import { createMessageTable, createMessageTableIndex, createOwnerTable, insertOwner, } from "./Sql.js";
10
+ import { Sqlite, queryObjectToQuery, } from "./Sqlite.js";
11
+ import { makeInitialTimestamp, timestampToString } from "./Timestamp.js";
12
+ const commonColumns = ["createdAt", "updatedAt", "isDeleted"];
13
+ const kysely = new Kysely.Kysely({
14
+ dialect: {
15
+ createAdapter: () => new Kysely.SqliteAdapter(),
16
+ createDriver: () => new Kysely.DummyDriver(),
17
+ createIntrospector() {
18
+ throw "Not implemeneted";
19
+ },
20
+ createQueryCompiler: () => new Kysely.SqliteQueryCompiler(),
21
+ },
22
+ });
23
+ export const makeCreateQuery = () => (queryCallback) => queryObjectToQuery(queryCallback(kysely).compile());
24
+ // https://github.com/Effect-TS/schema/releases/tag/v0.18.0
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ const getPropertySignatures = (schema) => {
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const out = {};
29
+ const propertySignatures = AST.getPropertySignatures(schema.ast);
30
+ for (let i = 0; i < propertySignatures.length; i++) {
31
+ const propertySignature = propertySignatures[i];
32
+ out[propertySignature.name] = make(propertySignature.type);
33
+ }
34
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any
35
+ return out;
36
+ };
37
+ export const schemaToTables = (
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ schema) => pipe(getPropertySignatures(schema), ReadonlyRecord.toEntries, ReadonlyArray.map(([name, schema]) => ({
40
+ name,
41
+ columns: Object.keys(getPropertySignatures(schema)).concat(commonColumns),
42
+ })));
43
+ export const Owner = Context.Tag("evolu/Owner");
44
+ export const transaction = (effect) => Effect.flatMap(Sqlite, (sqlite) => Effect.acquireUseRelease(sqlite.exec("BEGIN"), () => effect, (_, exit) => Exit.isFailure(exit) ? sqlite.exec("ROLLBACK") : sqlite.exec("END")));
45
+ export const someDefectToNoSuchTableOrColumnError = Effect.catchSomeDefect((error) => {
46
+ if (typeof error === "object" &&
47
+ error != null &&
48
+ "message" in error &&
49
+ typeof error.message === "string" &&
50
+ error.message.includes("code 1") &&
51
+ (error.message.includes("no such table") ||
52
+ error.message.includes("no such column") ||
53
+ error.message.includes("has no column")))
54
+ return Option.some(Effect.fail({
55
+ _tag: "NoSuchTableOrColumnError",
56
+ }));
57
+ return Option.none();
58
+ });
59
+ export const makeOwner = (mnemonic) => Effect.gen(function* (_) {
60
+ const bip39 = yield* _(Bip39);
61
+ if (mnemonic == null)
62
+ mnemonic = yield* _(bip39.make);
63
+ const seed = yield* _(bip39.toSeed(mnemonic));
64
+ const id = yield* _(slip21Derive(seed, ["Evolu", "Owner Id"]).pipe(Effect.map((key) => {
65
+ // convert key to nanoid
66
+ let id = "";
67
+ for (let i = 0; i < 21; i++) {
68
+ id += urlAlphabet[key[i] & 63];
69
+ }
70
+ return id;
71
+ })));
72
+ const encryptionKey = yield* _(slip21Derive(seed, ["Evolu", "Encryption Key"]));
73
+ return { mnemonic, id, encryptionKey };
74
+ });
75
+ export const lazyInit = (mnemonic) => Effect.gen(function* (_) {
76
+ const [owner, sqlite, initialTimestampString] = yield* _(Effect.all([makeOwner(mnemonic), Sqlite, makeInitialTimestamp], {
77
+ concurrency: "unbounded",
78
+ }));
79
+ yield* _(Effect.all([
80
+ sqlite.exec(createMessageTable),
81
+ sqlite.exec(createMessageTableIndex),
82
+ sqlite.exec(createOwnerTable),
83
+ sqlite.exec({
84
+ sql: insertOwner,
85
+ parameters: [
86
+ owner.id,
87
+ owner.mnemonic,
88
+ // expo-sqlite 11.3.2 doesn't support Uint8Array
89
+ bytesToHex(owner.encryptionKey),
90
+ timestampToString(initialTimestampString),
91
+ merkleTreeToString(initialMerkleTree),
92
+ ],
93
+ }),
94
+ ]));
95
+ return owner;
96
+ });
97
+ const getTables = Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec(`SELECT "name" FROM "sqlite_schema" WHERE "type" = 'table'`)), Effect.map((result) => result.rows), Effect.map(ReadonlyArray.map((row) => row.name + "")), Effect.map(ReadonlyArray.filter(Predicate.not(String.startsWith("__")))), Effect.map(ReadonlyArray.dedupeWith(String.Equivalence)));
98
+ const updateTable = ({ name, columns, }) => Effect.gen(function* (_) {
99
+ const sqlite = yield* _(Sqlite);
100
+ const sql = yield* _(sqlite.exec(`PRAGMA table_info (${name})`), Effect.map((result) => result.rows), Effect.map(ReadonlyArray.map((row) => row.name)), Effect.map((existingColumns) => ReadonlyArray.differenceWith(String.Equivalence)(existingColumns)(columns)), Effect.map(ReadonlyArray.map((newColumn) => `ALTER TABLE "${name}" ADD COLUMN "${newColumn}" blob;`)), Effect.map(ReadonlyArray.join("")));
101
+ if (sql)
102
+ yield* _(sqlite.exec(sql));
103
+ });
104
+ const createTable = ({ name, columns, }) => Effect.flatMap(Sqlite, (sqlite) => sqlite.exec(`
105
+ CREATE TABLE ${name} (
106
+ "id" text primary key,
107
+ ${columns
108
+ .filter((c) => c !== "id")
109
+ // "A column with affinity BLOB does not prefer one storage class over another
110
+ // and no attempt is made to coerce data from one storage class into another."
111
+ // https://www.sqlite.org/datatype3.html
112
+ .map((name) => `"${name}" blob`)
113
+ .join(", ")}
114
+ );
115
+ `));
116
+ export const ensureSchema = (tables) => Effect.flatMap(getTables, (existingTables) => Effect.forEach(tables, (tableDefinition) => existingTables.includes(tableDefinition.name)
117
+ ? updateTable(tableDefinition)
118
+ : createTable(tableDefinition), { discard: true }));
@@ -0,0 +1,78 @@
1
+ import { Brand, Context, Layer, ReadonlyArray, ReadonlyRecord } from "effect";
2
+ import { Config } from "./Config.js";
3
+ import { Bip39, Mnemonic, NanoId } from "./Crypto.js";
4
+ import { Owner, Tables } from "./Db.js";
5
+ import { QueryPatches } from "./Diff.js";
6
+ import { EvoluError } from "./Errors.js";
7
+ import { Id, SqliteDate } from "./Model.js";
8
+ import { Query, Row, Sqlite, Value } from "./Sqlite.js";
9
+ import { SyncState, SyncWorker, SyncWorkerOutputSyncResponse } from "./SyncWorker.js";
10
+ export interface DbWorker {
11
+ readonly postMessage: (input: DbWorkerInput) => void;
12
+ onMessage: (output: DbWorkerOutput) => void;
13
+ }
14
+ export declare const DbWorker: Context.Tag<DbWorker, DbWorker>;
15
+ export type DbWorkerInput = DbWorkerInputInit | DbWorkerInputQuery | DbWorkerInputMutate | DbWorkerInputSync | DbWorkerInputReset | DbWorkerInputEnsureSchema | SyncWorkerOutputSyncResponse;
16
+ interface DbWorkerInputInit {
17
+ readonly _tag: "init";
18
+ readonly config: Config;
19
+ readonly tables: Tables;
20
+ }
21
+ interface DbWorkerInputQuery {
22
+ readonly _tag: "query";
23
+ readonly queries: ReadonlyArray.NonEmptyReadonlyArray<Query>;
24
+ }
25
+ interface DbWorkerInputMutate {
26
+ readonly _tag: "mutate";
27
+ readonly items: ReadonlyArray.NonEmptyReadonlyArray<MutateItem>;
28
+ readonly queries: ReadonlyArray<Query>;
29
+ }
30
+ interface DbWorkerInputSync {
31
+ readonly _tag: "sync";
32
+ readonly queries: ReadonlyArray<Query>;
33
+ }
34
+ interface DbWorkerInputReset {
35
+ readonly _tag: "reset";
36
+ readonly mnemonic?: Mnemonic;
37
+ }
38
+ interface DbWorkerInputEnsureSchema {
39
+ readonly _tag: "ensureSchema";
40
+ readonly tables: Tables;
41
+ }
42
+ export type DbWorkerOutput = DbWorkerOutputOnError | DbWorkerOutputOnOwner | DbWorkerOutputOnQuery | DbWorkerOutputOnReceive | DbWorkerOutputOnResetOrRestore | DbWorkerOutputOnSyncState;
43
+ interface DbWorkerOutputOnError {
44
+ readonly _tag: "onError";
45
+ readonly error: EvoluError;
46
+ }
47
+ interface DbWorkerOutputOnOwner {
48
+ readonly _tag: "onOwner";
49
+ readonly owner: Owner;
50
+ }
51
+ export interface DbWorkerOutputOnQuery {
52
+ readonly _tag: "onQuery";
53
+ readonly queriesPatches: ReadonlyArray<QueryPatches>;
54
+ readonly onCompleteIds: ReadonlyArray<OnCompleteId>;
55
+ }
56
+ export type OnCompleteId = string & Brand.Brand<"Id"> & Brand.Brand<"OnComplete">;
57
+ interface DbWorkerOutputOnReceive {
58
+ readonly _tag: "onReceive";
59
+ }
60
+ interface DbWorkerOutputOnResetOrRestore {
61
+ readonly _tag: "onResetOrRestore";
62
+ }
63
+ interface DbWorkerOutputOnSyncState {
64
+ readonly _tag: "onSyncState";
65
+ readonly state: SyncState;
66
+ }
67
+ export interface MutateItem {
68
+ readonly table: string;
69
+ readonly id: Id;
70
+ readonly values: ReadonlyRecord.ReadonlyRecord<Value | Date | boolean | undefined>;
71
+ readonly isInsert: boolean;
72
+ readonly now: SqliteDate;
73
+ readonly onCompleteId: OnCompleteId | null;
74
+ }
75
+ export type RowsCacheMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
76
+ export declare const DbWorkerLive: Layer.Layer<Bip39 | NanoId | Sqlite | SyncWorker, never, DbWorker>;
77
+ export {};
78
+ //# sourceMappingURL=DbWorker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DbWorker.d.ts","sourceRoot":"","sources":["../../src/DbWorker.ts"],"names":[],"mappings":"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"}