@evolu/common 5.4.8 → 6.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 (242) hide show
  1. package/README.md +30 -34
  2. package/dist/src/Array.d.ts +17 -0
  3. package/dist/src/Array.d.ts.map +1 -0
  4. package/dist/src/Array.js +12 -0
  5. package/dist/src/Assert.d.ts +68 -0
  6. package/dist/src/Assert.d.ts.map +1 -0
  7. package/dist/src/Assert.js +77 -0
  8. package/dist/src/BigInt.d.ts +20 -0
  9. package/dist/src/BigInt.d.ts.map +1 -0
  10. package/dist/src/BigInt.js +18 -0
  11. package/dist/src/Buffer.d.ts +92 -0
  12. package/dist/src/Buffer.d.ts.map +1 -0
  13. package/dist/src/Buffer.js +62 -0
  14. package/dist/src/Callbacks.d.ts +20 -0
  15. package/dist/src/Callbacks.d.ts.map +1 -0
  16. package/dist/src/Callbacks.js +18 -0
  17. package/dist/src/Console.d.ts +78 -0
  18. package/dist/src/Console.d.ts.map +1 -0
  19. package/dist/src/Console.js +103 -0
  20. package/dist/src/Crypto.d.ts +72 -39
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +89 -54
  23. package/dist/src/Eq.d.ts +97 -0
  24. package/dist/src/Eq.d.ts.map +1 -0
  25. package/dist/src/Eq.js +167 -0
  26. package/dist/src/Error.d.ts +14 -10
  27. package/dist/src/Error.d.ts.map +1 -1
  28. package/dist/src/Error.js +43 -11
  29. package/dist/src/Evolu/Config.d.ts +69 -0
  30. package/dist/src/Evolu/Config.d.ts.map +1 -0
  31. package/dist/src/Evolu/Config.js +9 -0
  32. package/dist/src/Evolu/Db.d.ts +126 -0
  33. package/dist/src/Evolu/Db.d.ts.map +1 -0
  34. package/dist/src/Evolu/Db.js +774 -0
  35. package/dist/src/Evolu/Diff.d.ts +43 -0
  36. package/dist/src/Evolu/Diff.d.ts.map +1 -0
  37. package/dist/src/Evolu/Diff.js +95 -0
  38. package/dist/src/Evolu/Evolu.d.ts +334 -0
  39. package/dist/src/Evolu/Evolu.d.ts.map +1 -0
  40. package/dist/src/Evolu/Evolu.js +434 -0
  41. package/dist/src/Evolu/Internal.d.ts +26 -0
  42. package/dist/src/Evolu/Internal.d.ts.map +1 -0
  43. package/dist/src/Evolu/Internal.js +25 -0
  44. package/dist/src/Evolu/Kysely.d.ts +6 -0
  45. package/dist/src/Evolu/Kysely.d.ts.map +1 -0
  46. package/dist/src/Evolu/Kysely.js +21 -0
  47. package/dist/src/Evolu/Owner.d.ts +155 -0
  48. package/dist/src/Evolu/Owner.d.ts.map +1 -0
  49. package/dist/src/Evolu/Owner.js +126 -0
  50. package/dist/src/Evolu/Platform.d.ts +23 -0
  51. package/dist/src/Evolu/Platform.d.ts.map +1 -0
  52. package/dist/src/Evolu/Platform.js +1 -0
  53. package/dist/src/Evolu/Protocol.d.ts +401 -0
  54. package/dist/src/Evolu/Protocol.d.ts.map +1 -0
  55. package/dist/src/Evolu/Protocol.js +1151 -0
  56. package/dist/src/Evolu/Public.d.ts +18 -0
  57. package/dist/src/Evolu/Public.d.ts.map +1 -0
  58. package/dist/src/Evolu/Public.js +11 -0
  59. package/dist/src/Evolu/PublicKysely.d.ts +148 -0
  60. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
  61. package/dist/src/Evolu/PublicKysely.js +185 -0
  62. package/dist/src/Evolu/Query.d.ts +63 -0
  63. package/dist/src/Evolu/Query.d.ts.map +1 -0
  64. package/dist/src/Evolu/Query.js +61 -0
  65. package/dist/src/Evolu/Relay.d.ts +13 -0
  66. package/dist/src/Evolu/Relay.d.ts.map +1 -0
  67. package/dist/src/Evolu/Relay.js +109 -0
  68. package/dist/src/Evolu/Schema.d.ts +201 -0
  69. package/dist/src/Evolu/Schema.d.ts.map +1 -0
  70. package/dist/src/Evolu/Schema.js +150 -0
  71. package/dist/src/Evolu/Storage.d.ts +49 -0
  72. package/dist/src/Evolu/Storage.d.ts.map +1 -0
  73. package/dist/src/Evolu/Storage.js +1111 -0
  74. package/dist/src/Evolu/Sync.d.ts +59 -0
  75. package/dist/src/Evolu/Sync.d.ts.map +1 -0
  76. package/dist/src/Evolu/Sync.js +29 -0
  77. package/dist/src/Evolu/Timestamp.d.ts +106 -0
  78. package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
  79. package/dist/src/Evolu/Timestamp.js +179 -0
  80. package/dist/src/Function.d.ts +54 -0
  81. package/dist/src/Function.d.ts.map +1 -0
  82. package/dist/src/Function.js +38 -0
  83. package/dist/src/ManyToManyMap.d.ts +26 -0
  84. package/dist/src/ManyToManyMap.d.ts.map +1 -0
  85. package/dist/src/ManyToManyMap.js +92 -0
  86. package/dist/src/NanoId.d.ts +27 -0
  87. package/dist/src/NanoId.d.ts.map +1 -0
  88. package/dist/src/NanoId.js +6 -0
  89. package/dist/src/Number.d.ts +42 -0
  90. package/dist/src/Number.d.ts.map +1 -0
  91. package/dist/src/Number.js +55 -0
  92. package/dist/src/Object.d.ts +35 -0
  93. package/dist/src/Object.d.ts.map +1 -0
  94. package/dist/src/Object.js +36 -0
  95. package/dist/src/Order.d.ts +90 -0
  96. package/dist/src/Order.d.ts.map +1 -0
  97. package/dist/src/Order.js +85 -0
  98. package/dist/src/Promise.d.ts +180 -0
  99. package/dist/src/Promise.d.ts.map +1 -0
  100. package/dist/src/Promise.js +176 -0
  101. package/dist/src/Random.d.ts +52 -0
  102. package/dist/src/Random.d.ts.map +1 -0
  103. package/dist/src/Random.js +29 -0
  104. package/dist/src/Ref.d.ts +40 -0
  105. package/dist/src/Ref.d.ts.map +1 -0
  106. package/dist/src/Ref.js +13 -0
  107. package/dist/src/Result.d.ts +421 -0
  108. package/dist/src/Result.d.ts.map +1 -0
  109. package/dist/src/Result.js +357 -0
  110. package/dist/src/Skiplist.d.ts +23 -0
  111. package/dist/src/Skiplist.d.ts.map +1 -0
  112. package/dist/src/Skiplist.js +58 -0
  113. package/dist/src/Sqlite.d.ts +116 -52
  114. package/dist/src/Sqlite.d.ts.map +1 -1
  115. package/dist/src/Sqlite.js +183 -67
  116. package/dist/src/Store.d.ts +45 -8
  117. package/dist/src/Store.d.ts.map +1 -1
  118. package/dist/src/Store.js +33 -17
  119. package/dist/src/String.d.ts +2 -0
  120. package/dist/src/String.d.ts.map +1 -0
  121. package/dist/src/String.js +14 -0
  122. package/dist/src/Time.d.ts +20 -0
  123. package/dist/src/Time.d.ts.map +1 -0
  124. package/dist/src/Time.js +25 -0
  125. package/dist/src/Type.d.ts +1937 -0
  126. package/dist/src/Type.d.ts.map +1 -0
  127. package/dist/src/Type.js +2002 -0
  128. package/dist/src/Types.d.ts +188 -0
  129. package/dist/src/Types.d.ts.map +1 -0
  130. package/dist/src/Types.js +6 -0
  131. package/dist/src/WebSocket.d.ts +112 -0
  132. package/dist/src/WebSocket.d.ts.map +1 -0
  133. package/dist/src/WebSocket.js +139 -0
  134. package/dist/src/Worker.d.ts +44 -0
  135. package/dist/src/Worker.d.ts.map +1 -0
  136. package/dist/src/Worker.js +66 -0
  137. package/dist/src/index.d.ts +24 -11
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +24 -11
  140. package/package.json +29 -38
  141. package/src/Array.ts +39 -0
  142. package/src/Assert.ts +116 -0
  143. package/src/BigInt.ts +29 -0
  144. package/src/Buffer.ts +175 -0
  145. package/src/Callbacks.ts +43 -0
  146. package/src/Console.ts +159 -0
  147. package/src/Crypto.ts +169 -115
  148. package/src/Eq.ts +204 -0
  149. package/src/Error.ts +57 -20
  150. package/src/Evolu/Config.ts +83 -0
  151. package/src/Evolu/Db.ts +1275 -0
  152. package/src/Evolu/Diff.ts +142 -0
  153. package/src/Evolu/Evolu.ts +947 -0
  154. package/src/Evolu/Internal.ts +26 -0
  155. package/src/Evolu/Kysely.ts +38 -0
  156. package/src/Evolu/Owner.ts +296 -0
  157. package/src/Evolu/Platform.ts +27 -0
  158. package/src/Evolu/Protocol.ts +1857 -0
  159. package/src/Evolu/Public.ts +43 -0
  160. package/src/Evolu/PublicKysely.ts +240 -0
  161. package/src/Evolu/Query.ts +167 -0
  162. package/src/Evolu/Relay.ts +142 -0
  163. package/src/Evolu/Schema.ts +417 -0
  164. package/src/Evolu/Storage.ts +1281 -0
  165. package/src/Evolu/Sync.ts +105 -0
  166. package/src/Evolu/Timestamp.ts +311 -0
  167. package/src/Function.ts +58 -0
  168. package/src/ManyToManyMap.ts +140 -0
  169. package/src/NanoId.ts +39 -0
  170. package/src/Number.ts +90 -0
  171. package/src/Object.ts +64 -0
  172. package/src/Order.ts +113 -0
  173. package/src/Promise.ts +295 -0
  174. package/src/Random.ts +68 -0
  175. package/src/Ref.ts +63 -0
  176. package/src/Result.ts +453 -0
  177. package/src/Skiplist.ts +102 -0
  178. package/src/Sqlite.ts +366 -153
  179. package/src/Store.ts +79 -36
  180. package/src/String.ts +10 -0
  181. package/src/Time.ts +36 -0
  182. package/src/Type.ts +3978 -0
  183. package/src/Types.ts +209 -0
  184. package/src/WebSocket.ts +273 -0
  185. package/src/Worker.ts +129 -0
  186. package/src/index.ts +24 -11
  187. package/dist/src/Config.d.ts +0 -56
  188. package/dist/src/Config.d.ts.map +0 -1
  189. package/dist/src/Config.js +0 -39
  190. package/dist/src/Crdt.d.ts +0 -89
  191. package/dist/src/Crdt.d.ts.map +0 -1
  192. package/dist/src/Crdt.js +0 -181
  193. package/dist/src/Db.d.ts +0 -107
  194. package/dist/src/Db.d.ts.map +0 -1
  195. package/dist/src/Db.js +0 -443
  196. package/dist/src/Diff.d.ts +0 -27
  197. package/dist/src/Diff.d.ts.map +0 -1
  198. package/dist/src/Diff.js +0 -84
  199. package/dist/src/Evolu.d.ts +0 -426
  200. package/dist/src/Evolu.d.ts.map +0 -1
  201. package/dist/src/Evolu.js +0 -333
  202. package/dist/src/Model.d.ts +0 -141
  203. package/dist/src/Model.d.ts.map +0 -1
  204. package/dist/src/Model.js +0 -125
  205. package/dist/src/Murmurhash.d.ts +0 -2
  206. package/dist/src/Murmurhash.d.ts.map +0 -1
  207. package/dist/src/Murmurhash.js +0 -60
  208. package/dist/src/Owner.d.ts +0 -33
  209. package/dist/src/Owner.d.ts.map +0 -1
  210. package/dist/src/Owner.js +0 -26
  211. package/dist/src/Platform.d.ts +0 -37
  212. package/dist/src/Platform.d.ts.map +0 -1
  213. package/dist/src/Platform.js +0 -11
  214. package/dist/src/Protobuf.d.ts +0 -81
  215. package/dist/src/Protobuf.d.ts.map +0 -1
  216. package/dist/src/Protobuf.js +0 -92
  217. package/dist/src/Public.d.ts +0 -13
  218. package/dist/src/Public.d.ts.map +0 -1
  219. package/dist/src/Public.js +0 -6
  220. package/dist/src/Socket.d.ts +0 -8
  221. package/dist/src/Socket.d.ts.map +0 -1
  222. package/dist/src/Socket.js +0 -51
  223. package/dist/src/Sql.d.ts +0 -12
  224. package/dist/src/Sql.d.ts.map +0 -1
  225. package/dist/src/Sql.js +0 -30
  226. package/dist/src/Sync.d.ts +0 -70
  227. package/dist/src/Sync.d.ts.map +0 -1
  228. package/dist/src/Sync.js +0 -127
  229. package/src/Config.ts +0 -119
  230. package/src/Crdt.ts +0 -361
  231. package/src/Db.ts +0 -955
  232. package/src/Diff.ts +0 -114
  233. package/src/Evolu.ts +0 -1016
  234. package/src/Model.ts +0 -233
  235. package/src/Murmurhash.ts +0 -70
  236. package/src/Owner.ts +0 -69
  237. package/src/Platform.ts +0 -47
  238. package/src/Protobuf.ts +0 -155
  239. package/src/Public.ts +0 -12
  240. package/src/Socket.ts +0 -83
  241. package/src/Sql.ts +0 -41
  242. package/src/Sync.ts +0 -315
package/src/Evolu.ts DELETED
@@ -1,1016 +0,0 @@
1
- import * as AST from "@effect/schema/AST";
2
- import * as S from "@effect/schema/Schema";
3
- import { make } from "@effect/schema/Schema";
4
- import * as Arr from "effect/Array";
5
- import * as Context from "effect/Context";
6
- import * as Effect from "effect/Effect";
7
- import { constVoid, flow, pipe } from "effect/Function";
8
- import * as Layer from "effect/Layer";
9
- import * as ManagedRuntime from "effect/ManagedRuntime";
10
- import * as Number from "effect/Number";
11
- import * as Option from "effect/Option";
12
- import * as Predicate from "effect/Predicate";
13
- import * as Record from "effect/Record";
14
- import * as Kysely from "kysely";
15
- import { Config, createRuntime, defaultConfig } from "./Config.js";
16
- import { TimestampString } from "./Crdt.js";
17
- import { Mnemonic, NanoIdGenerator } from "./Crypto.js";
18
- import { createSocket } from "./Socket.js";
19
- import {
20
- DbFactory,
21
- DbSchema,
22
- Index,
23
- Mutation,
24
- Queries,
25
- Query,
26
- QueryResult,
27
- QueryResultsPromisesFromQueries,
28
- QueryRowsMap,
29
- Row,
30
- Table,
31
- deserializeQuery,
32
- emptyRows,
33
- queryResultFromRows,
34
- serializeQuery,
35
- } from "./Db.js";
36
- import { QueryPatches, applyPatches } from "./Diff.js";
37
- import { EvoluError, makeUnexpectedError } from "./Error.js";
38
- import { Id, SqliteBoolean, SqliteDate } from "./Model.js";
39
- import { Owner } from "./Owner.js";
40
- import { AppState, FlushSync } from "./Platform.js";
41
- import {
42
- SqliteQuery,
43
- SqliteQueryOptions,
44
- Value,
45
- isSqlMutation,
46
- } from "./Sqlite.js";
47
- import { Listener, Unsubscribe, makeStore } from "./Store.js";
48
- import { SyncState, initialSyncState } from "./Sync.js";
49
-
50
- /**
51
- * The Evolu interface provides a type-safe SQL query building and state
52
- * management defined by a database schema. It leverages Kysely for creating SQL
53
- * queries in TypeScript, enabling operations such as data querying, loading,
54
- * subscription to data changes, and mutations (create, update, createOrUpdate).
55
- * It also includes functionalities for error handling, syncing state
56
- * management, and owner data manipulation. Specifically, Evolu allows:
57
- *
58
- * - Subscribing to and getting errors via subscribeError and getError.
59
- * - Creating type-safe SQL queries with createQuery, leveraging Kysely's
60
- * capabilities.
61
- * - Loading queries and subscribing to query result changes using loadQuery,
62
- * loadQueries, subscribeQuery, and getQuery.
63
- * - Subscribing to and getting the owner's information and sync state changes.
64
- * - Performing mutations on the database with create, update, and createOrUpdate
65
- * methods, which include automatic management of common columns like
66
- * createdAt, updatedAt, and isDeleted.
67
- * - Managing owner data with resetOwner and restoreOwner.
68
- * - Ensuring the database schema's integrity with ensureSchema.
69
- */
70
- export interface Evolu<T extends EvoluSchema = EvoluSchema> {
71
- readonly socket: Promise<WebSocket | undefined>;
72
- /**
73
- * Subscribe to {@link EvoluError} changes.
74
- *
75
- * @example
76
- * const unsubscribe = evolu.subscribeError(() => {
77
- * const error = evolu.getError();
78
- * console.log(error);
79
- * });
80
- */
81
- readonly subscribeError: (listener: Listener) => Unsubscribe;
82
-
83
- /** Get {@link EvoluError}. */
84
- readonly getError: () => EvoluError | null;
85
-
86
- /**
87
- * Create type-safe SQL {@link Query}.
88
- *
89
- * Evolu uses Kysely - the type-safe SQL query builder for TypeScript. See
90
- * https://kysely.dev.
91
- *
92
- * For mutations, use {@link create} and {@link update}.
93
- *
94
- * @example
95
- * const allTodos = evolu.createQuery((db) =>
96
- * db.selectFrom("todo").selectAll(),
97
- * );
98
- *
99
- * const todoById = (id: TodoId) =>
100
- * evolu.createQuery((db) =>
101
- * db.selectFrom("todo").selectAll().where("id", "=", id),
102
- * );
103
- */
104
- readonly createQuery: <R extends Row>(
105
- queryCallback: (
106
- db: Pick<
107
- Kysely.Kysely<
108
- {
109
- [Table in keyof T]: NullableExceptIdCreatedAtUpdatedAt<{
110
- [Column in keyof T[Table]]: T[Table][Column];
111
- }>;
112
- } & {
113
- readonly evolu_message: {
114
- readonly timestamp: TimestampString;
115
- readonly table: keyof T;
116
- readonly row: Id;
117
- readonly column: string;
118
- readonly value: Value;
119
- };
120
- }
121
- >,
122
- "selectFrom" | "fn" | "with" | "withRecursive"
123
- >,
124
- ) => Kysely.SelectQueryBuilder<any, any, R>,
125
- options?: SqliteQueryOptions,
126
- ) => Query<R>;
127
-
128
- /**
129
- * Load {@link Query} and return a promise with {@link QueryResult}.
130
- *
131
- * A returned promise always resolves successfully because there is no reason
132
- * why loading should fail. All data are local, and the query is typed. A
133
- * serious unexpected Evolu error shall be handled with
134
- * {@link subscribeError}.
135
- *
136
- * Loading is batched, and returned promises are cached, so there is no need
137
- * for an additional cache. Evolu's internal cache is invalidated on
138
- * mutation.
139
- *
140
- * The returned promise is enriched with special status and value properties
141
- * for the upcoming React `use` Hook, but other UI libraries can also leverage
142
- * them. Speaking of React, there are two essential React Suspense-related
143
- * patterns that every developer should be aware of—passing promises to
144
- * children and caching over mutations.
145
- *
146
- * With promises passed to children, we can load a query as soon as possible,
147
- * but we don't have to use the returned promise immediately. That's useful
148
- * for prefetching, which is generally not necessary for local-first apps but
149
- * can be if a query takes a long time to load.
150
- *
151
- * Caching over mutation is a pattern that every developer should know. As we
152
- * said, Evolu caches promise until a mutation happens. A query loaded after
153
- * that will return a new pending promise. That's okay for general usage but
154
- * not for UI with React Suspense because a mutation would suspend rerendered
155
- * queries on a page, and that's not a good UX.
156
- *
157
- * We call this pattern "caching over mutation" because it has no globally
158
- * accepted name yet. React RFC for React Cache does not exist yet.
159
- *
160
- * For better UX, a query must be subscribed for updates. This way, instead of
161
- * Suspense flashes, the user sees new data immediately because Evolu replaces
162
- * cached promises with fresh, already resolved new ones.
163
- *
164
- * If you are curious why Evolu does not do that for all queries by default,
165
- * the answer is simple: performance. Tracking changes is costly and
166
- * meaningful only for visible (hence subscribed) queries anyway. To subscribe
167
- * to a query, use {@link subscribeQuery}.
168
- *
169
- * @example
170
- * const allTodos = evolu.createQuery((db) =>
171
- * db.selectFrom("todo").selectAll(),
172
- * );
173
- * evolu.loadQuery(allTodos).then(({ rows }) => {
174
- * console.log(rows);
175
- * });
176
- */
177
- readonly loadQuery: <R extends Row>(
178
- query: Query<R>,
179
- ) => Promise<QueryResult<R>>;
180
-
181
- /**
182
- * Load an array of {@link Query} queries and return an array of
183
- * {@link QueryResult} promises. It's like `queries.map(loadQuery)` but with
184
- * proper types for returned promises.
185
- *
186
- * @example
187
- * evolu.loadQueries([allTodos, todoById(1)]);
188
- */
189
- readonly loadQueries: <R extends Row, Q extends Queries<R>>(
190
- queries: [...Q],
191
- ) => [...QueryResultsPromisesFromQueries<Q>];
192
-
193
- /**
194
- * Subscribe to {@link Query} {@link QueryResult} changes.
195
- *
196
- * @example
197
- * const unsubscribe = evolu.subscribeQuery(allTodos)(() => {
198
- * const { rows } = evolu.getQuery(allTodos);
199
- * });
200
- */
201
- readonly subscribeQuery: (
202
- query: Query,
203
- ) => (listener: Listener) => Unsubscribe;
204
-
205
- /**
206
- * Get {@link Query} {@link QueryResult}.
207
- *
208
- * @example
209
- * const unsubscribe = evolu.subscribeQuery(allTodos)(() => {
210
- * const { rows } = evolu.getQuery(allTodos);
211
- * });
212
- */
213
- readonly getQuery: <R extends Row>(query: Query<R>) => QueryResult<R>;
214
-
215
- /**
216
- * Subscribe to {@link Owner} changes.
217
- *
218
- * @example
219
- * const unsubscribe = evolu.subscribeOwner(() => {
220
- * const owner = evolu.getOwner();
221
- * });
222
- */
223
- readonly subscribeOwner: (listener: Listener) => Unsubscribe;
224
-
225
- /**
226
- * Get {@link Owner}.
227
- *
228
- * @example
229
- * const unsubscribe = evolu.subscribeOwner(() => {
230
- * const owner = evolu.getOwner();
231
- * });
232
- */
233
- readonly getOwner: () => Owner | null;
234
-
235
- /**
236
- * Subscribe to {@link SyncState} changes.
237
- *
238
- * @example
239
- * const unsubscribe = evolu.subscribeSyncState(() => {
240
- * const syncState = evolu.getSyncState();
241
- * });
242
- */
243
- readonly subscribeSyncState: (listener: Listener) => Unsubscribe;
244
-
245
- /**
246
- * Get {@link SyncState}.
247
- *
248
- * @example
249
- * const unsubscribe = evolu.subscribeSyncState(() => {
250
- * const syncState = evolu.getSyncState();
251
- * });
252
- */
253
- readonly getSyncState: () => SyncState;
254
-
255
- /**
256
- * Create a row in the database and returns a new ID. The first argument is
257
- * the table name, and the second is an object.
258
- *
259
- * The third optional argument, the onComplete callback, is generally
260
- * unnecessary because creating a row cannot fail. Still, UI libraries can use
261
- * it to ensure the DOM is updated if we want to manipulate it, for example,
262
- * to focus an element.
263
- *
264
- * Evolu does not use SQL for mutations to ensure data can be safely and
265
- * predictably merged without conflicts.
266
- *
267
- * Explicit mutations also allow Evolu to automatically add and update a few
268
- * useful columns common to all tables. Those columns are: `createdAt`,
269
- * `updatedAt`, and `isDeleted`.
270
- *
271
- * @example
272
- * import * as S from "@effect/schema/Schema";
273
- *
274
- * // Evolu uses the Schema to enforce domain model.
275
- * const title = S.decodeSync(Evolu.NonEmptyString1000)("A title");
276
- *
277
- * const { id } = evolu.create("todo", { title }, () => {
278
- * // onComplete callback
279
- * });
280
- */
281
- create: Mutate<T, "create">;
282
-
283
- /**
284
- * Update a row in the database and return the existing ID. The first argument
285
- * is the table name, and the second is an object.
286
- *
287
- * The third optional argument, the onComplete callback, is generally
288
- * unnecessary because updating a row cannot fail. Still, UI libraries can use
289
- * it to ensure the DOM is updated if we want to manipulate it, for example,
290
- * to focus an element.
291
- *
292
- * Evolu does not use SQL for mutations to ensure data can be safely and
293
- * predictably merged without conflicts.
294
- *
295
- * Explicit mutations also allow Evolu to automatically add and update a few
296
- * useful columns common to all tables. Those columns are: `createdAt`,
297
- * `updatedAt`, and `isDeleted`.
298
- *
299
- * @example
300
- * import * as S from "@effect/schema/Schema";
301
- *
302
- * // Evolu uses the Schema to enforce domain model.
303
- * const title = S.decodeSync(Evolu.NonEmptyString1000)("A title");
304
- * evolu.update("todo", { id, title });
305
- *
306
- * // To delete a row, set `isDeleted` to true.
307
- * evolu.update("todo", { id, isDeleted: true });
308
- */
309
- update: Mutate<T, "update">;
310
-
311
- /**
312
- * Create or update a row in the database and return the existing ID. The
313
- * first argument is the table name, and the second is an object.
314
- *
315
- * This function is useful when we already have an `id` and want to create a
316
- * new row or update an existing one.
317
- *
318
- * The third optional argument, the onComplete callback, is generally
319
- * unnecessary because updating a row cannot fail. Still, UI libraries can use
320
- * it to ensure the DOM is updated if we want to manipulate it, for example,
321
- * to focus an element.
322
- *
323
- * Evolu does not use SQL for mutations to ensure data can be safely and
324
- * predictably merged without conflicts.
325
- *
326
- * Explicit mutations also allow Evolu to automatically add and update a few
327
- * useful columns common to all tables. Those columns are: `createdAt`,
328
- * `updatedAt`, and `isDeleted`.
329
- *
330
- * @example
331
- * import * as S from "@effect/schema/Schema";
332
- * import { Id } from "@evolu/react";
333
- *
334
- * // Id can be stable.
335
- * // 2024-02-0800000000000
336
- * const id = S.decodeSync(Id)(date.toString().padEnd(21, "0")) as TodoId;
337
- *
338
- * evolu.createOrUpdate("todo", { id, title });
339
- */
340
- createOrUpdate: Mutate<T, "createOrUpdate">;
341
-
342
- /**
343
- * Delete {@link Owner} and all their data from the current device. After the
344
- * deletion, Evolu will purge the application state. For browsers, this will
345
- * reload all tabs using Evolu. For native apps, it will restart the app.
346
- *
347
- * Reloading can be turned off via options if you want to provide a different
348
- * UX.
349
- */
350
- readonly resetOwner: (options?: {
351
- readonly reload: boolean;
352
- }) => Promise<void>;
353
-
354
- /**
355
- * Restore {@link Owner} with all their synced data. It uses {@link resetOwner},
356
- * so be careful.
357
- */
358
- readonly restoreOwner: (
359
- mnemonic: Mnemonic,
360
- options?: {
361
- readonly reload: boolean;
362
- },
363
- ) => Promise<void>;
364
-
365
- /**
366
- * Reload the app in a platform-specific way. For browsers, this will reload
367
- * all tabs using Evolu. For native apps, it will restart the app.
368
- */
369
- readonly reloadApp: () => void;
370
-
371
- /**
372
- * Ensure tables and columns defined in {@link EvoluSchema} exist in the
373
- * database.
374
- *
375
- * This function is for hot/live reloading.
376
- */
377
- readonly ensureSchema: (schema: DbSchema) => void;
378
-
379
- /** Export SQLite database as Uint8Array. */
380
- readonly exportDatabase: () => Promise<Uint8Array>;
381
- }
382
-
383
- /** A type to define tables, columns, and column types. */
384
- export type EvoluSchema = Record.ReadonlyRecord<
385
- string,
386
- Record.ReadonlyRecord<string, Value> & {
387
- readonly id: Id;
388
- }
389
- >;
390
-
391
- type NullableExceptIdCreatedAtUpdatedAt<T> = {
392
- readonly [K in keyof T]: K extends "id" | "createdAt" | "updatedAt"
393
- ? T[K]
394
- : T[K] | null;
395
- };
396
-
397
- type Mutate<
398
- T extends EvoluSchema = EvoluSchema,
399
- Mode extends "create" | "update" | "createOrUpdate" = "update",
400
- > = <K extends keyof T>(
401
- table: K,
402
- values: Kysely.Simplify<
403
- Mode extends "create"
404
- ? PartialForNullable<
405
- Castable<Omit<T[K], "id" | "createdAt" | "updatedAt" | "isDeleted">>
406
- >
407
- : Mode extends "update"
408
- ? Partial<Castable<Omit<T[K], "id" | "createdAt" | "updatedAt">>> & {
409
- readonly id: T[K]["id"];
410
- }
411
- : PartialForNullable<
412
- Castable<Omit<T[K], "createdAt" | "updatedAt" | "isDeleted">>
413
- >
414
- >,
415
- onComplete?: MutateOnComplete,
416
- ) => {
417
- readonly id: T[K]["id"];
418
- };
419
-
420
- type MutateOnComplete = () => void;
421
-
422
- // https://stackoverflow.com/a/54713648/233902
423
- type PartialForNullable<
424
- T,
425
- NK extends keyof T = {
426
- [K in keyof T]: null extends T[K] ? K : never;
427
- }[keyof T],
428
- NP = Pick<T, Exclude<keyof T, NK>> & Partial<Pick<T, NK>>,
429
- > = { [K in keyof NP]: NP[K] };
430
-
431
- /**
432
- * SQLite doesn't support Date nor Boolean types, so Evolu emulates them with
433
- * {@link SqliteBoolean} and {@link SqliteDate}.
434
- */
435
- type Castable<T> = {
436
- readonly [K in keyof T]: T[K] extends SqliteBoolean
437
- ? boolean | SqliteBoolean
438
- : T[K] extends null | SqliteBoolean
439
- ? null | boolean | SqliteBoolean
440
- : T[K] extends SqliteDate
441
- ? Date | SqliteDate
442
- : T[K] extends null | SqliteDate
443
- ? null | Date | SqliteDate
444
- : T[K];
445
- };
446
-
447
- export class EvoluFactory extends Context.Tag("EvoluFactory")<
448
- EvoluFactory,
449
- {
450
- /**
451
- * Create Evolu from the database schema.
452
- *
453
- * Tables with a name prefixed with `_` are local-only, which means they are
454
- * never synced. It's useful for device-specific or temporal data.
455
- *
456
- * @example
457
- * import * as S from "@effect/schema/Schema";
458
- * import * as E from "@evolu/react";
459
- * // The same API for different platforms
460
- * // import * as E from "@evolu/react-native";
461
- * // import * as E from "@evolu/common-web";
462
- *
463
- * const TodoId = E.id("Todo");
464
- * type TodoId = typeof TodoId.Type;
465
- *
466
- * const TodoTable = E.table({
467
- * id: TodoId,
468
- * title: E.NonEmptyString1000,
469
- * });
470
- * type TodoTable = typeof TodoTable.Type;
471
- *
472
- * const Database = E.database({
473
- * todo: TodoTable,
474
- *
475
- * // Prefix `_` makes the table local-only (it will not sync)
476
- * _todo: TodoTable,
477
- * });
478
- * type Database = typeof Database.Type;
479
- *
480
- * const evolu = E.createEvolu(Database);
481
- */
482
- readonly createEvolu: <T extends EvoluSchema, I>(
483
- schema: S.Schema<T, I>,
484
- config?: Partial<EvoluConfig<T>>,
485
- ) => Evolu<T>;
486
- }
487
- >() {
488
- static Common = Layer.effect(
489
- EvoluFactory,
490
- Effect.gen(function* () {
491
- const flushSync = yield* Effect.map(
492
- Effect.serviceOption(FlushSync),
493
- Option.getOrElse<FlushSync>(() => (callback) => callback()),
494
- );
495
-
496
- const context = Context.empty().pipe(
497
- Context.add(DbFactory, yield* DbFactory),
498
- Context.add(NanoIdGenerator, yield* NanoIdGenerator),
499
- Context.add(FlushSync, flushSync),
500
- Context.add(AppState, yield* AppState),
501
- );
502
-
503
- // For hot/live reloading and future Evolu dynamic import.
504
- const instances = new Map<string, Evolu>();
505
-
506
- return EvoluFactory.of({
507
- createEvolu: <T extends EvoluSchema, I>(
508
- schema: S.Schema<T, I>,
509
- {
510
- indexes,
511
- initialData,
512
- mnemonic,
513
- ...config
514
- }: Partial<EvoluConfig<T>> = {},
515
- ): Evolu<T> => {
516
- const runtime = createRuntime(config);
517
- const name = config?.name || defaultConfig.name;
518
- const dbSchema: DbSchema = {
519
- tables: schemaToTables(schema),
520
- indexes: indexes || [],
521
- };
522
- let evolu = instances.get(name);
523
- if (evolu == null) {
524
- evolu = createEvolu(
525
- dbSchema,
526
- runtime,
527
- initialData as EvoluConfig["initialData"],
528
- mnemonic,
529
- ).pipe(Effect.provide(context), runtime.runSync);
530
- instances.set(name, evolu);
531
- } else {
532
- evolu.ensureSchema(dbSchema);
533
- }
534
- return evolu as Evolu<T>;
535
- },
536
- });
537
- }),
538
- );
539
- }
540
-
541
- export interface EvoluConfig<T extends EvoluSchema = EvoluSchema>
542
- extends Config {
543
- /**
544
- * Use the `indexes` option to define SQLite indexes.
545
- *
546
- * Table and column names are not typed because Kysely doesn't support it.
547
- *
548
- * https://medium.com/@JasonWyatt/squeezing-performance-from-sqlite-indexes-indexes-c4e175f3c346
549
- *
550
- * @example
551
- * const indexes = [
552
- * createIndex("indexTodoCreatedAt").on("todo").column("createdAt"),
553
- *
554
- * createIndex("indexTodoCategoryCreatedAt")
555
- * .on("todoCategory")
556
- * .column("createdAt"),
557
- * ];
558
- */
559
- indexes: ReadonlyArray<Index>;
560
-
561
- /** Use this option to create initial data (fixtures). */
562
- initialData: (evolu: EvoluForInitialData<T>) => void;
563
-
564
- /**
565
- * Use this option to create Evolu with the specified mnemonic. If omitted,
566
- * the mnemonic will be autogenerated. That should be the default behavior
567
- * until special UX requirements are needed (e.g., multitenancy).
568
- */
569
- mnemonic: Mnemonic;
570
- }
571
-
572
- const schemaToTables = (schema: S.Schema<any>) =>
573
- pipe(
574
- getPropertySignatures(schema),
575
- Record.toEntries,
576
- Arr.map(
577
- ([name, schema]): Table => ({
578
- name,
579
- columns: Object.keys(getPropertySignatures(schema)),
580
- }),
581
- ),
582
- );
583
-
584
- // TODO: Simplify.
585
- // https://discord.com/channels/795981131316985866/1218626687546294386/1218796529725476935
586
- const getPropertySignatures = <I extends { [K in keyof A]: any }, A>(
587
- schema: S.Schema<A, I>,
588
- ): { [K in keyof A]: S.Schema<A[K], I[K]> } => {
589
- const out: Record<PropertyKey, S.Schema<any>> = {};
590
- const propertySignatures = AST.getPropertySignatures(schema.ast);
591
- for (let i = 0; i < propertySignatures.length; i++) {
592
- const propertySignature = propertySignatures[i];
593
- out[propertySignature.name] = make(propertySignature.type);
594
- }
595
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
596
- return out as any;
597
- };
598
-
599
- const createEvolu = (
600
- schema: DbSchema,
601
- runtime: ManagedRuntime.ManagedRuntime<Config, never>,
602
- initialData: EvoluConfig["initialData"],
603
- mnemonic: Mnemonic | undefined,
604
- ) =>
605
- Effect.gen(function* () {
606
- yield* Effect.logTrace("EvoluFactory createEvolu");
607
- const config = yield* Config;
608
- const dbFactory = yield* DbFactory;
609
- const appState = yield* AppState;
610
- const nanoIdGenerator = yield* NanoIdGenerator;
611
- const flushSync = yield* FlushSync;
612
-
613
- const db = yield* dbFactory.createDb;
614
- const errorStore = yield* makeStore<EvoluError | null>(null);
615
- const ownerStore = yield* makeStore<Owner | null>(null);
616
- const rowsStore = yield* makeStore<QueryRowsMap>(new Map());
617
- const syncStateStore = yield* makeStore<SyncState>(initialSyncState);
618
-
619
- const loadingPromises = new Map<Query, LoadingPromise>();
620
- const subscribedQueries = new Map<Query, number>();
621
-
622
- const handleAllErrors = <T>(effect: Effect.Effect<T, EvoluError, Config>) =>
623
- effect.pipe(
624
- Effect.catchAllDefect((error) =>
625
- Effect.fail(makeUnexpectedError(error)),
626
- ),
627
- Effect.tapError(Effect.logError),
628
- Effect.tapError(errorStore.setState),
629
- );
630
-
631
- const runFork = flow(handleAllErrors, runtime.runFork);
632
- const runSync = flow(handleAllErrors, runtime.runSync);
633
- const runPromise = flow(handleAllErrors, runtime.runPromise);
634
-
635
- const initialDataAsMutations = yield* Effect.provideService(
636
- initialDataToMutations(initialData),
637
- NanoIdGenerator,
638
- nanoIdGenerator,
639
- );
640
-
641
- const handleDbError = (error: EvoluError) => {
642
- Effect.fail(error).pipe(runFork);
643
- };
644
-
645
- const handleSyncStateChange = (state: SyncState) => {
646
- Effect.logDebug(["Evolu handleSyncStateChange", { state }]).pipe(
647
- Effect.zipRight(syncStateStore.setState(state)),
648
- runFork,
649
- );
650
- };
651
-
652
- const handleDbReceive = () => {
653
- Effect.gen(function* () {
654
- yield* Effect.logTrace("Evolu handleDbReceive");
655
- releaseUnsubscribedLoadingPromises();
656
- const queries = [...subscribedQueries.keys()];
657
- if (queries.length > 0) {
658
- yield* Effect.flatMap(db.loadQueries(queries), handlePatches());
659
- }
660
- }).pipe(runFork);
661
- };
662
-
663
- const sync =
664
- ({ refreshQueries }: { refreshQueries: boolean }) =>
665
- () => {
666
- Effect.flatMap(
667
- db.sync(refreshQueries ? [...subscribedQueries.keys()] : []),
668
- handlePatches(),
669
- ).pipe(runFork);
670
- };
671
-
672
- db.init(
673
- schema,
674
- initialDataAsMutations,
675
- handleDbError,
676
- handleSyncStateChange,
677
- handleDbReceive,
678
- mnemonic,
679
- ).pipe(
680
- Effect.tap(sync({ refreshQueries: false })),
681
- Effect.flatMap(ownerStore.setState),
682
- Effect.catchTag("NotSupportedPlatformError", () => Effect.void), // no-op
683
- runFork,
684
- );
685
- let socketResolved = false;
686
- const socket: Promise<WebSocket | undefined> = new Promise((resolve) => {
687
- ownerStore.subscribe(() => {
688
- const owner = ownerStore.getState() as Owner;
689
- if (!socketResolved && typeof owner?.id === "string") {
690
- socketResolved = true;
691
- resolve(createSocket(sync, config, owner));
692
- }
693
- });
694
- });
695
-
696
- const appStateReset = yield* appState.init({
697
- onRequestSync: sync({ refreshQueries: true }),
698
- reloadUrl: config.reloadUrl,
699
- });
700
-
701
- const handlePatches =
702
- (options?: {
703
- /**
704
- * The flushSync is for onComplete handlers only. For example, with
705
- * React, when we want to focus on a node created by a mutation, we must
706
- * ensure all DOM changes are flushed synchronously.
707
- */
708
- readonly flushSync: boolean;
709
- }) =>
710
- (patches: ReadonlyArray<QueryPatches>) =>
711
- Effect.logDebug(["Evolu handlePatches", { patches }]).pipe(
712
- Effect.zipRight(rowsStoreStateFromPatches(patches)),
713
- Effect.tap((nextState) =>
714
- Effect.forEach(patches, ({ query }) =>
715
- resolveLoadingPromises(
716
- query,
717
- nextState.get(query) || emptyRows(),
718
- ),
719
- ),
720
- ),
721
- Effect.tap((nextState) => {
722
- if (options?.flushSync) {
723
- flushSync(() => {
724
- rowsStore.setState(nextState).pipe(runSync);
725
- });
726
- } else {
727
- rowsStore.setState(nextState).pipe(runSync);
728
- }
729
- }),
730
- );
731
-
732
- const rowsStoreStateFromPatches = (patches: ReadonlyArray<QueryPatches>) =>
733
- Effect.sync((): QueryRowsMap => {
734
- const rowsStoreState = rowsStore.getState();
735
- if (patches.length === 0) return rowsStoreState;
736
- const queriesRows = Arr.map(
737
- patches,
738
- ({ query, patches }): [Query, ReadonlyArray<Row>] => [
739
- query,
740
- applyPatches(patches, rowsStoreState.get(query) || emptyRows()),
741
- ],
742
- );
743
- return new Map([...rowsStoreState, ...queriesRows]);
744
- });
745
-
746
- const resolveLoadingPromises = (query: Query, rows: ReadonlyArray<Row>) =>
747
- Effect.sync(() => {
748
- const loadingPromise = loadingPromises.get(query);
749
- if (!loadingPromise) return;
750
- const result = queryResultFromRows(rows);
751
- if (loadingPromise.promise.status !== "fulfilled") {
752
- loadingPromise.resolve(result);
753
- } else {
754
- // A promise can't be fulfilled 2x, so we need a new one.
755
- loadingPromise.promise = Promise.resolve(result);
756
- }
757
- /**
758
- * "For example, a data framework can set the status and value fields on
759
- * a promise preemptively, before passing to React, so that React can
760
- * unwrap it without waiting a microtask."
761
- * https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
762
- */
763
- Object.assign(loadingPromise.promise, {
764
- status: "fulfilled",
765
- value: result,
766
- });
767
- if (loadingPromise.releaseOnResolve) {
768
- loadingPromises.delete(query);
769
- }
770
- });
771
-
772
- /**
773
- * We can't delete loading promises in `resolveLoadingPromises` because they
774
- * must be cached, so repeated calls to `loadQuery` will always return the
775
- * same promise until the data changes, and we also can't cache them forever
776
- * because only subscribed queries are automatically updated (reactivity is
777
- * expensive) hence this function must be called manually on any mutation.
778
- */
779
- const releaseUnsubscribedLoadingPromises = () => {
780
- [...loadingPromises.entries()]
781
- .filter(([query]) => !subscribedQueries.has(query))
782
- .forEach(([query, loadingPromise]) => {
783
- if (loadingPromise.promise.status === "fulfilled") {
784
- loadingPromises.delete(query);
785
- } else {
786
- loadingPromise.releaseOnResolve = true;
787
- }
788
- });
789
- };
790
-
791
- const mutate = ((): Mutate => {
792
- let queue: ReadonlyArray<[Mutation, MutateOnComplete | undefined]> = [];
793
- return (table, { id, ...values }, onComplete) => {
794
- Effect.logDebug(["Evolu mutate", { table, id, values }]).pipe(runSync);
795
- const isInsert = id == null;
796
- if (isInsert) id = nanoIdGenerator.rowId.pipe(runSync);
797
- queue = [...queue, [{ table, id, values, isInsert }, onComplete]];
798
- if (queue.length === 1)
799
- queueMicrotask(() => {
800
- const [mutations, onCompletes] = Arr.unzip(queue);
801
- queue = [];
802
- const onCompletesDef = onCompletes.filter(Predicate.isNotUndefined);
803
- releaseUnsubscribedLoadingPromises();
804
- db.mutate(mutations, [...subscribedQueries.keys()]).pipe(
805
- Effect.flatMap(
806
- handlePatches({ flushSync: onCompletesDef.length > 0 }),
807
- ),
808
- Effect.tap(() => {
809
- onCompletesDef.forEach((onComplete) => onComplete());
810
- }),
811
- runFork,
812
- );
813
- });
814
- return { id };
815
- };
816
- })();
817
-
818
- const evolu: Evolu = {
819
- socket,
820
- subscribeError: errorStore.subscribe,
821
- getError: errorStore.getState,
822
-
823
- createQuery: (queryCallback, options) =>
824
- pipe(
825
- queryCallback(kysely as never).compile(),
826
- (compiledQuery): SqliteQuery => {
827
- if (isSqlMutation(compiledQuery.sql))
828
- throw new Error(
829
- "SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu mutation API.",
830
- );
831
- const parameters = compiledQuery.parameters as NonNullable<
832
- SqliteQuery["parameters"]
833
- >;
834
- return {
835
- sql: compiledQuery.sql,
836
- parameters,
837
- ...(options && { options }),
838
- };
839
- },
840
- (query) => serializeQuery(query),
841
- ),
842
-
843
- loadQuery: (() => {
844
- let queue: ReadonlyArray<Query> = [];
845
- return <R extends Row>(query: Query<R>): Promise<QueryResult<R>> => {
846
- Effect.logDebug([
847
- "Evolu loadQuery",
848
- { query: deserializeQuery(query) },
849
- ]).pipe(runSync);
850
- let loadingPromise = loadingPromises.get(query);
851
- if (!loadingPromise) {
852
- let resolve: LoadingPromise["resolve"] = constVoid;
853
- const promise: LoadingPromise["promise"] = new Promise(
854
- (_resolve) => {
855
- resolve = _resolve;
856
- },
857
- );
858
- loadingPromise = { resolve, promise, releaseOnResolve: false };
859
- loadingPromises.set(query, loadingPromise);
860
- queue = [...queue, query];
861
- if (queue.length === 1) {
862
- queueMicrotask(() => {
863
- db.loadQueries(Arr.dedupe(queue)).pipe(
864
- Effect.flatMap(handlePatches()),
865
- runFork,
866
- );
867
- queue = [];
868
- });
869
- }
870
- }
871
- return loadingPromise.promise as Promise<QueryResult<R>>;
872
- };
873
- })(),
874
-
875
- loadQueries: <R extends Row, Q extends Queries<R>>(
876
- queries: [...Q],
877
- ): [...QueryResultsPromisesFromQueries<Q>] =>
878
- queries.map(evolu.loadQuery) as [...QueryResultsPromisesFromQueries<Q>],
879
-
880
- subscribeQuery: (query) => (listener) => {
881
- subscribedQueries.set(
882
- query,
883
- Number.increment(subscribedQueries.get(query) ?? 0),
884
- );
885
- const unsubscribe = rowsStore.subscribe(listener);
886
-
887
- return () => {
888
- const count = subscribedQueries.get(query);
889
- if (count != null && count > 1)
890
- subscribedQueries.set(query, Number.decrement(count));
891
- else subscribedQueries.delete(query);
892
- unsubscribe();
893
- };
894
- },
895
-
896
- getQuery: <R extends Row>(query: Query<R>): QueryResult<R> =>
897
- queryResultFromRows(
898
- rowsStore.getState().get(query) || emptyRows(),
899
- ) as QueryResult<R>,
900
-
901
- subscribeOwner: ownerStore.subscribe,
902
- getOwner: ownerStore.getState,
903
-
904
- subscribeSyncState: syncStateStore.subscribe,
905
- getSyncState: syncStateStore.getState,
906
-
907
- create: mutate as Mutate<EvoluSchema, "create">,
908
- update: mutate,
909
- createOrUpdate: mutate as Mutate<EvoluSchema, "createOrUpdate">,
910
-
911
- resetOwner: (options) =>
912
- Effect.gen(function* () {
913
- yield* db.resetOwner();
914
- if (options?.reload !== false) yield* appStateReset.reset;
915
- }).pipe(runPromise),
916
-
917
- restoreOwner: (mnemonic, options) =>
918
- Effect.gen(function* () {
919
- yield* db.restoreOwner(schema, mnemonic);
920
- if (options?.reload !== false) yield* appStateReset.reset;
921
- }).pipe(runPromise),
922
-
923
- reloadApp: () => {
924
- appStateReset.reset.pipe(runFork);
925
- },
926
-
927
- ensureSchema: (schema) => {
928
- db.ensureSchema(schema).pipe(runFork);
929
- },
930
-
931
- exportDatabase: () => db.exportDatabase().pipe(runPromise),
932
- };
933
-
934
- return evolu;
935
- });
936
-
937
- const initialDataToMutations = (
938
- initialData: EvoluConfig["initialData"] = constVoid,
939
- ) =>
940
- Effect.map(NanoIdGenerator, (nanoIdGenerator) => {
941
- const mutations: Mutation[] = [];
942
- const mutate: Mutate = (table, { id, ...values }) => {
943
- if (id == null) id = nanoIdGenerator.rowId.pipe(Effect.runSync) as never;
944
- mutations.push({ isInsert: true, id, table: table as string, values });
945
- return { id };
946
- };
947
- const evolu: EvoluForInitialData = {
948
- create: mutate as Mutate<EvoluSchema, "create">,
949
- createOrUpdate: mutate as Mutate<EvoluSchema, "createOrUpdate">,
950
- };
951
- initialData(evolu);
952
- return mutations;
953
- });
954
-
955
- interface EvoluForInitialData<T extends EvoluSchema = EvoluSchema> {
956
- create: Mutate<T, "create">;
957
- createOrUpdate: Mutate<T, "createOrUpdate">;
958
- }
959
-
960
- interface LoadingPromise {
961
- /** Promise with props for the upcoming React use hook. */
962
- promise: Promise<QueryResult> & {
963
- status?: "pending" | "fulfilled" | "rejected";
964
- value?: QueryResult;
965
- reason?: unknown;
966
- };
967
- resolve: (rows: QueryResult) => void;
968
- releaseOnResolve: boolean;
969
- }
970
-
971
- // https://kysely.dev/docs/recipes/splitting-query-building-and-execution
972
- const kysely = new Kysely.Kysely({
973
- dialect: {
974
- createAdapter: () => new Kysely.SqliteAdapter(),
975
- createDriver: () => new Kysely.DummyDriver(),
976
- createIntrospector() {
977
- // eslint-disable-next-line @typescript-eslint/only-throw-error
978
- throw "Not implemeneted";
979
- },
980
- createQueryCompiler: () => new Kysely.SqliteQueryCompiler(),
981
- },
982
- });
983
-
984
- const createIndex = kysely.schema.createIndex.bind(kysely.schema);
985
- type CreateIndex = typeof createIndex;
986
-
987
- /**
988
- * Create SQLite indexes.
989
- *
990
- * See https://www.evolu.dev/docs/indexes
991
- *
992
- * @example
993
- * const indexes = createIndexes((create) => [
994
- * create("indexTodoCreatedAt").on("todo").column("createdAt"),
995
- * create("indexTodoCategoryCreatedAt")
996
- * .on("todoCategory")
997
- * .column("createdAt"),
998
- * ]);
999
- */
1000
- export const createIndexes = (
1001
- callback: (
1002
- create: CreateIndex,
1003
- ) => ReadonlyArray<Kysely.CreateIndexBuilder<any>>,
1004
- ): ReadonlyArray<Index> =>
1005
- callback(createIndex).map(
1006
- (index): Index => ({
1007
- name: index.toOperationNode().name.name,
1008
- sql: index.compile().sql,
1009
- }),
1010
- );
1011
-
1012
- /** Create a namespaced lock name. */
1013
- export const getLockName = (
1014
- name: string,
1015
- ): Effect.Effect<string, never, Config> =>
1016
- Effect.map(Config, (config) => `evolu:${config.name}:${name}`);