@evolu/common 5.4.7 → 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/Db.ts DELETED
@@ -1,955 +0,0 @@
1
- import * as S from "@effect/schema/Schema";
2
- import * as Arr from "effect/Array";
3
- import * as Brand from "effect/Brand";
4
- import * as Console from "effect/Console";
5
- import * as Context from "effect/Context";
6
- import * as Deferred from "effect/Deferred";
7
- import * as Effect from "effect/Effect";
8
- import { Equivalence } from "effect/Equivalence";
9
- import { constVoid, pipe } from "effect/Function";
10
- import * as Option from "effect/Option";
11
- import * as Predicate from "effect/Predicate";
12
- import * as Record from "effect/Record";
13
- import * as Ref from "effect/Ref";
14
- import * as Schedule from "effect/Schedule";
15
- import * as String from "effect/String";
16
- import * as SynchronizedRef from "effect/SynchronizedRef";
17
- import * as Kysely from "kysely";
18
- import { Config } from "./Config.js";
19
- import {
20
- MerkleTree,
21
- Millis,
22
- Time,
23
- Timestamp,
24
- TimestampCounterOverflowError,
25
- TimestampDriftError,
26
- TimestampString,
27
- TimestampTimeOutOfRangeError,
28
- diffMerkleTrees,
29
- initialMerkleTree,
30
- insertIntoMerkleTree,
31
- makeInitialTimestamp,
32
- makeSyncTimestamp,
33
- merkleTreeToString,
34
- receiveTimestamp,
35
- sendTimestamp,
36
- timestampToString,
37
- unsafeTimestampFromString,
38
- } from "./Crdt.js";
39
- import { Mnemonic, NanoIdGenerator } from "./Crypto.js";
40
- import { QueryPatches, makePatches } from "./Diff.js";
41
- import {
42
- EvoluError,
43
- ensureTransferableError,
44
- makeUnexpectedError,
45
- } from "./Error.js";
46
- import { Id, cast } from "./Model.js";
47
- import { Owner, OwnerId, makeOwner } from "./Owner.js";
48
- import { SyncLock } from "./Platform.js";
49
- import * as Sql from "./Sql.js";
50
- import {
51
- JsonObjectOrArray,
52
- Sqlite,
53
- SqliteExecResult,
54
- SqliteFactory,
55
- SqliteQuery,
56
- SqliteQueryOptions,
57
- SqliteQueryPlanRow,
58
- SqliteTransactionMode,
59
- Value,
60
- drawSqliteQueryPlan,
61
- isJsonObjectOrArray,
62
- } from "./Sqlite.js";
63
- import {
64
- Message,
65
- NewMessage,
66
- Sync,
67
- SyncData,
68
- SyncFactory,
69
- SyncResult,
70
- SyncState,
71
- } from "./Sync.js";
72
-
73
- export interface Db {
74
- readonly init: (
75
- schema: DbSchema,
76
- initialData: ReadonlyArray<Mutation>,
77
- onError: Callbacks["onError"],
78
- onSyncStateChange: Callbacks["onSyncStateChange"],
79
- onReceive: Callbacks["onReceive"],
80
- mnemonic: Mnemonic | undefined,
81
- ) => Effect.Effect<
82
- Owner,
83
- | NotSupportedPlatformError
84
- | TimestampTimeOutOfRangeError
85
- | TimestampDriftError
86
- | TimestampCounterOverflowError,
87
- Config
88
- >;
89
-
90
- readonly loadQueries: (
91
- queries: ReadonlyArray<Query>,
92
- ) => Effect.Effect<ReadonlyArray<QueryPatches>>;
93
-
94
- readonly mutate: (
95
- mutations: ReadonlyArray<Mutation>,
96
- queriesToRefresh: ReadonlyArray<Query>,
97
- ) => Effect.Effect<
98
- ReadonlyArray<QueryPatches>,
99
- | TimestampTimeOutOfRangeError
100
- | TimestampDriftError
101
- | TimestampCounterOverflowError,
102
- Config
103
- >;
104
-
105
- readonly resetOwner: () => Effect.Effect<void>;
106
-
107
- readonly restoreOwner: (
108
- schema: DbSchema,
109
- mnemonic: Mnemonic,
110
- ) => Effect.Effect<void>;
111
-
112
- readonly ensureSchema: (schema: DbSchema) => Effect.Effect<void>;
113
-
114
- readonly sync: (
115
- queriesToRefresh: ReadonlyArray<Query>,
116
- ) => Effect.Effect<ReadonlyArray<QueryPatches>, never, Config>;
117
-
118
- readonly exportDatabase: () => Effect.Effect<Uint8Array>;
119
- }
120
-
121
- export interface DbSchema {
122
- readonly tables: ReadonlyArray<Table>;
123
- readonly indexes: ReadonlyArray<Index>;
124
- }
125
-
126
- export interface Table {
127
- readonly name: string;
128
- readonly columns: ReadonlyArray<string>;
129
- }
130
-
131
- export const Index = S.Struct({
132
- name: S.String,
133
- sql: S.String,
134
- });
135
- export type Index = typeof Index.Type;
136
-
137
- export interface Mutation {
138
- readonly table: string;
139
- readonly id: Id;
140
- readonly values: Record.ReadonlyRecord<
141
- string,
142
- Value | Date | boolean | undefined
143
- >;
144
- readonly isInsert: boolean;
145
- }
146
-
147
- export interface NotSupportedPlatformError {
148
- readonly _tag: "NotSupportedPlatformError";
149
- }
150
-
151
- // https://blog.beraliv.dev/2021-05-07-opaque-type-in-typescript
152
- declare const __queryBrand: unique symbol;
153
-
154
- /**
155
- * Query is SQL query serialized as a string with a branded type representing a
156
- * row it returns.
157
- */
158
- export type Query<R extends Row = Row> = string &
159
- Brand.Brand<"Query"> & { readonly [__queryBrand]: R };
160
-
161
- export type Row = {
162
- [key: string]:
163
- | Value
164
- | Row // for jsonObjectFrom from kysely/helpers/sqlite
165
- | ReadonlyArray<Row>; // for jsonArrayFrom from kysely/helpers/sqlite
166
- };
167
-
168
- export class DbFactory extends Context.Tag("DbFactory")<
169
- DbFactory,
170
- {
171
- readonly createDb: Effect.Effect<Db>;
172
- }
173
- >() {}
174
-
175
- export const createDb: Effect.Effect<
176
- Db,
177
- never,
178
- SqliteFactory | NanoIdGenerator | Time | SyncFactory | SyncLock
179
- > = Effect.gen(function* () {
180
- const { createSqlite } = yield* SqliteFactory;
181
- const { createSync } = yield* SyncFactory;
182
-
183
- const initContext = Context.empty().pipe(
184
- Context.add(NanoIdGenerator, yield* NanoIdGenerator),
185
- Context.add(Time, yield* Time),
186
- Context.add(SyncLock, yield* SyncLock),
187
- );
188
-
189
- const afterInitContext =
190
- yield* Deferred.make<
191
- Context.Context<
192
- NanoIdGenerator | Time | SyncLock | Sqlite | Owner | Sync | Callbacks
193
- >
194
- >();
195
-
196
- const afterInit =
197
- (options: { readonly transaction: SqliteTransactionMode }) =>
198
- <A, E, R>(effect: Effect.Effect<A, E, R>) =>
199
- Effect.flatMap(Deferred.await(afterInitContext), (context) =>
200
- Effect.flatMap(Sqlite, (sqlite) =>
201
- sqlite.transaction(options.transaction)(effect),
202
- ).pipe(Effect.provide(context)),
203
- );
204
-
205
- const queryRowsRef = yield* SynchronizedRef.make<QueryRowsMap>(new Map());
206
-
207
- const db: Db = {
208
- init: (
209
- schema,
210
- initialData,
211
- onError,
212
- onSyncStateChange,
213
- onReceive,
214
- mnemonic,
215
- ) =>
216
- Effect.gen(function* () {
217
- yield* Effect.logDebug(["Db init", { schema }]);
218
- const sqlite = yield* createSqlite;
219
- const contextWithSqlite = Context.add(initContext, Sqlite, sqlite);
220
- const owner = yield* getSchema.pipe(
221
- Effect.tap(ensureSchema(schema)),
222
- Effect.flatMap((currentSchema) => {
223
- if (currentSchema.tables.map((t) => t.name).includes("evolu_owner"))
224
- return readOwner;
225
- return createOwner(mnemonic).pipe(
226
- Effect.tap(applyMutations(initialData)),
227
- );
228
- }),
229
- sqlite.transaction("exclusive"),
230
- Effect.provide(contextWithSqlite),
231
- );
232
- const sync = yield* createSync.pipe(Effect.provide(initContext));
233
- yield* sync.init(owner);
234
- Deferred.unsafeDone(
235
- afterInitContext,
236
- Effect.succeed(
237
- contextWithSqlite.pipe(
238
- Context.add(Owner, owner),
239
- Context.add(Sync, sync),
240
- Context.add(Callbacks, { onError, onSyncStateChange, onReceive }),
241
- ),
242
- ),
243
- );
244
- return owner;
245
- }),
246
-
247
- loadQueries: (queries) =>
248
- Effect.logDebug(["Db loadQueries", { queries }]).pipe(
249
- Effect.zipRight(loadQueries(queries, queryRowsRef)),
250
- afterInit({ transaction: "shared" }),
251
- ),
252
-
253
- mutate: (mutations, queriesToRefresh) =>
254
- Effect.gen(function* () {
255
- yield* Effect.logDebug(["Db mutate", { mutations, queriesToRefresh }]);
256
- const time = yield* Time;
257
- const sqlite = yield* Sqlite;
258
-
259
- const [toSyncMutations, localOnlyMutations] = Arr.partition(
260
- mutations,
261
- /** Table name starting with '_' is local only (not synced). */
262
- (mutation) => mutation.table.startsWith("_"),
263
- );
264
-
265
- for (const mutation of localOnlyMutations) {
266
- const isDeleteMutation = mutationToNewMessages(mutation).some(
267
- ({ column, value }) => column === "isDeleted" && value === 1,
268
- );
269
- if (isDeleteMutation) {
270
- yield* sqlite.exec({
271
- sql: `delete from "${mutation.table}" where "id" = ?;`,
272
- parameters: [mutation.id],
273
- });
274
- } else {
275
- const messages = mutationToNewMessages(mutation);
276
- for (const message of messages) {
277
- const now = yield* time.now;
278
- yield* upsertValueIntoTableRowColumn(message, messages, now);
279
- }
280
- }
281
- }
282
- if (toSyncMutations.length > 0) {
283
- yield* Effect.tap(applyMutations(toSyncMutations), forkSync);
284
- }
285
- return yield* loadQueries(queriesToRefresh, queryRowsRef);
286
- }).pipe(afterInit({ transaction: "exclusive" })),
287
-
288
- resetOwner: () =>
289
- Effect.logTrace("Db resetOwner").pipe(
290
- Effect.tap(dropAllTables),
291
- afterInit({ transaction: "last" }),
292
- ),
293
-
294
- restoreOwner: (schema, mnemonic) =>
295
- Effect.logTrace("Db restoreOwner").pipe(
296
- Effect.tap(dropAllTables),
297
- Effect.tap(Effect.flatMap(getSchema, ensureSchema(schema))),
298
- Effect.tap(createOwner(mnemonic)),
299
- afterInit({ transaction: "last" }),
300
- ),
301
-
302
- ensureSchema: (schema) =>
303
- getSchema.pipe(
304
- Effect.flatMap(ensureSchema(schema)),
305
- afterInit({ transaction: "exclusive" }),
306
- ),
307
-
308
- sync: (queriesToRefresh) =>
309
- Effect.logDebug(["Db sync", { queriesToRefresh }]).pipe(
310
- Effect.zipRight(forkSync()),
311
- Effect.zipRight(loadQueries(queriesToRefresh, queryRowsRef)),
312
- afterInit({ transaction: "shared" }),
313
- ),
314
-
315
- exportDatabase: () =>
316
- Effect.logTrace("Db exportDatabase").pipe(
317
- Effect.zipRight(Sqlite),
318
- Effect.flatMap((sqlite) => sqlite.export()),
319
- afterInit({ transaction: "exclusive" }),
320
- ),
321
-
322
- // TODO:
323
- // dispose: () =>
324
- // Effect.logTrace("Db dispose").pipe(
325
- // Effect.tap(Scope.close(scope, Exit.succeed("Db disposed"))),
326
- // afterInit({ transaction: "last" }),
327
- // ),
328
- };
329
-
330
- return db;
331
- });
332
-
333
- export interface Callbacks {
334
- readonly onError: (error: EvoluError) => void;
335
- readonly onSyncStateChange: (state: SyncState) => void;
336
- readonly onReceive: () => void;
337
- }
338
-
339
- export const Callbacks = Context.GenericTag<Callbacks>("Callbacks");
340
-
341
- export type QueryRowsMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
342
-
343
- const loadQueries = (
344
- queries: ReadonlyArray<Query>,
345
- queryRowsRef: SynchronizedRef.SynchronizedRef<QueryRowsMap>,
346
- ) =>
347
- Effect.gen(function* () {
348
- const sqlite = yield* Sqlite;
349
- const previousState = yield* SynchronizedRef.getAndUpdateEffect(
350
- queryRowsRef,
351
- (previousState) =>
352
- Effect.map(
353
- Effect.forEach(queries, (query) => {
354
- const sqliteQuery = deserializeQuery(query);
355
- return sqlite.exec(sqliteQuery).pipe(
356
- Effect.tap(maybeExplainQueryPlan(sqliteQuery)),
357
- Effect.map(({ rows }) => [query, rows] as const),
358
- );
359
- }),
360
- (queriesRows) => new Map([...previousState, ...queriesRows]),
361
- ),
362
- );
363
- const currentState = yield* SynchronizedRef.get(queryRowsRef);
364
- return queries.map(
365
- (query): QueryPatches => ({
366
- query,
367
- patches: makePatches(
368
- previousState.get(query),
369
- currentState.get(query) || [],
370
- ),
371
- }),
372
- );
373
- });
374
-
375
- const maybeExplainQueryPlan = (sqliteQuery: SqliteQuery) => {
376
- if (!sqliteQuery.options?.logExplainQueryPlan) return Effect.void;
377
- return Sqlite.pipe(
378
- Effect.flatMap((sqlite) =>
379
- sqlite.exec({
380
- ...sqliteQuery,
381
- sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
382
- }),
383
- ),
384
- Effect.tap(Console.log("ExplainQueryPlan", sqliteQuery)),
385
- Effect.tap(({ rows }) =>
386
- Console.log(drawSqliteQueryPlan(rows as SqliteQueryPlanRow[])),
387
- ),
388
- Effect.map(constVoid),
389
- );
390
- };
391
-
392
- const getSchema: Effect.Effect<DbSchema, never, Sqlite> = Effect.gen(
393
- function* (_) {
394
- yield* Effect.logTrace("Db getSchema");
395
- const sqlite = yield* Sqlite;
396
- const tables = yield* Effect.map(
397
- sqlite.exec({
398
- // https://til.simonwillison.net/sqlite/list-all-columns-in-a-database
399
- sql: `
400
- select
401
- sqlite_master.name as tableName,
402
- table_info.name as columnName
403
- from
404
- sqlite_master
405
- join pragma_table_info(sqlite_master.name) as table_info
406
- `.trim(),
407
- }),
408
- ({ rows }) => {
409
- const map = new Map<string, string[]>();
410
- rows.forEach((row) => {
411
- const { tableName, columnName } = row as {
412
- tableName: string;
413
- columnName: string;
414
- };
415
- if (!map.has(tableName)) map.set(tableName, []);
416
- map.get(tableName)?.push(columnName);
417
- });
418
- return globalThis.Array.from(map, ([name, columns]) => ({
419
- name,
420
- columns,
421
- }));
422
- },
423
- );
424
-
425
- const indexes = yield* Effect.map(
426
- sqlite.exec({
427
- sql: `
428
- select
429
- name, sql
430
- from
431
- sqlite_master
432
- where
433
- type='index' and
434
- name not like 'sqlite_%' and
435
- name not like 'index_evolu_%'
436
- `.trim(),
437
- }),
438
- (result) =>
439
- Arr.map(
440
- result.rows,
441
- (row): Index => ({
442
- name: row.name as string,
443
- /**
444
- * SQLite returns "CREATE INDEX" for "create index" for some reason.
445
- * Other keywords remain unchanged. We have to normalize the casing
446
- * for `indexEquivalence` manually.
447
- */
448
- sql: (row.sql as string).replace("CREATE INDEX", "create index"),
449
- }),
450
- ),
451
- );
452
-
453
- return { tables, indexes };
454
- },
455
- );
456
-
457
- const ensureSchema = (newSchema: DbSchema) => (currentSchema: DbSchema) =>
458
- Effect.gen(function* () {
459
- yield* Effect.logTrace("Db ensureSchema");
460
- const sql: string[] = [];
461
-
462
- newSchema.tables.forEach((table) => {
463
- const currentTable = currentSchema.tables.find(
464
- (t) => t.name === table.name,
465
- );
466
- if (!currentTable) {
467
- sql.push(
468
- `
469
- create table ${table.name} (
470
- "id" text primary key,
471
- ${table.columns
472
- .filter((c) => c !== "id")
473
- // "A column with affinity BLOB does not prefer one storage class over another
474
- // and no attempt is made to coerce data from one storage class into another."
475
- // https://www.sqlite.org/datatype3.html
476
- .map((name) => `"${name}" blob`)
477
- .join(", ")}
478
- );`.trim(),
479
- );
480
- } else {
481
- Arr.differenceWith(String.Equivalence)(
482
- table.columns,
483
- currentTable.columns,
484
- ).forEach((newColumn) => {
485
- sql.push(
486
- `alter table "${table.name}" add column "${newColumn}" blob;`,
487
- );
488
- });
489
- }
490
- });
491
-
492
- // Remove old indexes.
493
- Arr.differenceWith(indexEquivalence)(
494
- currentSchema.indexes,
495
- Arr.intersectionWith(indexEquivalence)(
496
- currentSchema.indexes,
497
- newSchema.indexes,
498
- ),
499
- ).forEach((indexToDrop) => {
500
- sql.push(`drop index "${indexToDrop.name}";`);
501
- });
502
-
503
- // Add new indexes.
504
- Arr.differenceWith(indexEquivalence)(
505
- newSchema.indexes,
506
- currentSchema.indexes,
507
- ).forEach((newIndex) => {
508
- sql.push(`${newIndex.sql};`);
509
- });
510
- if (sql.length > 0) {
511
- const sqlite = yield* Sqlite;
512
- yield* sqlite.exec({ sql: sql.join("\n") });
513
- }
514
- });
515
-
516
- const indexEquivalence: Equivalence<Index> = (self, that) =>
517
- self.name === that.name && self.sql === that.sql;
518
-
519
- const readOwner = Effect.logTrace("Db readOwner").pipe(
520
- Effect.zipRight(Sqlite),
521
- Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwner)),
522
- Effect.map(
523
- ({ rows: [row] }): Owner => ({
524
- id: row.id as OwnerId,
525
- mnemonic: row.mnemonic as Mnemonic,
526
- encryptionKey: row.encryptionKey as Uint8Array,
527
- }),
528
- ),
529
- );
530
-
531
- const createOwner = (mnemonic: Mnemonic | undefined) =>
532
- Effect.logTrace("Db createOwner").pipe(
533
- Effect.zipRight(
534
- Effect.all([makeOwner(mnemonic), Sqlite, makeInitialTimestamp]),
535
- ),
536
- Effect.tap(([owner, sqlite, initialTimestampString]) =>
537
- Effect.all([
538
- sqlite.exec(Sql.createMessageTable),
539
- sqlite.exec(Sql.createMessageTableIndex),
540
- sqlite.exec(Sql.createOwnerTable),
541
- sqlite.exec({
542
- ...Sql.insertOwner,
543
- parameters: [
544
- owner.id,
545
- owner.mnemonic,
546
- owner.encryptionKey,
547
- timestampToString(initialTimestampString),
548
- merkleTreeToString(initialMerkleTree),
549
- ],
550
- }),
551
- ]),
552
- ),
553
- Effect.map(([owner]) => owner),
554
- );
555
-
556
- const applyMutations = (mutations: ReadonlyArray<Mutation>) =>
557
- Effect.gen(function* (_) {
558
- const { timestamp, merkleTree } = yield* readTimestampAndMerkleTree;
559
- const [nextTimestamp, messages] = yield* Effect.mapAccum(
560
- mutations.flatMap(mutationToNewMessages),
561
- timestamp,
562
- (currentTimestamp, newMessage) =>
563
- Effect.map(sendTimestamp(currentTimestamp), (nextTimestamp) => {
564
- const message: Message = {
565
- ...newMessage,
566
- timestamp: timestampToString(nextTimestamp),
567
- };
568
- return [nextTimestamp, message];
569
- }),
570
- );
571
- const nextMerkleTree = yield* applyMessages(merkleTree, messages);
572
- yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
573
- return messages;
574
- });
575
-
576
- const readTimestampAndMerkleTree = Sqlite.pipe(
577
- Effect.flatMap((sqlite) =>
578
- sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree),
579
- ),
580
- Effect.map(({ rows: [{ timestamp, merkleTree }] }) => ({
581
- timestamp: unsafeTimestampFromString(timestamp as TimestampString),
582
- merkleTree: merkleTree as MerkleTree,
583
- })),
584
- );
585
-
586
- const mutationToNewMessages = (mutation: Mutation) =>
587
- pipe(
588
- Object.entries(mutation.values),
589
- Arr.filterMap(([column, value]) =>
590
- // The value can be undefined if exactOptionalPropertyTypes isn't true.
591
- // Don't insert nulls because null is the default value.
592
- value === undefined || (mutation.isInsert && value == null)
593
- ? Option.none()
594
- : Option.some([column, value] as const),
595
- ),
596
- Arr.map(
597
- ([column, value]): NewMessage => ({
598
- table: mutation.table,
599
- row: mutation.id,
600
- column,
601
- value:
602
- typeof value === "boolean"
603
- ? cast(value)
604
- : value instanceof Date
605
- ? cast(value)
606
- : value,
607
- }),
608
- ),
609
- );
610
-
611
- const applyMessages = (
612
- merkleTree: MerkleTree,
613
- messages: ReadonlyArray<Message>,
614
- ): Effect.Effect<MerkleTree, never, Sqlite> =>
615
- Effect.gen(function* () {
616
- const sqlite = yield* Sqlite;
617
- for (const message of messages) {
618
- const messageTimestamp = unsafeTimestampFromString(message.timestamp);
619
- const lastTimestamp = yield* Effect.map(
620
- sqlite.exec({
621
- ...Sql.selectLastTimestampForTableRowColumn,
622
- parameters: [message.table, message.row, message.column, 1],
623
- }),
624
- ({ rows }) =>
625
- rows.length > 0 ? (rows[0].timestamp as TimestampString) : null,
626
- );
627
- if (lastTimestamp == null || lastTimestamp < message.timestamp) {
628
- yield* upsertValueIntoTableRowColumn(
629
- message,
630
- messages,
631
- messageTimestamp.millis,
632
- );
633
- }
634
- if (lastTimestamp == null || lastTimestamp !== message.timestamp) {
635
- const { changes } = yield* sqlite.exec({
636
- ...Sql.insertIntoMessagesIfNew,
637
- parameters: [
638
- message.timestamp,
639
- message.table,
640
- message.row,
641
- message.column,
642
- message.value,
643
- ],
644
- });
645
- if (changes === 1)
646
- merkleTree = insertIntoMerkleTree(merkleTree, messageTimestamp);
647
- }
648
- }
649
- return merkleTree;
650
- });
651
-
652
- export const upsertValueIntoTableRowColumn = (
653
- message: NewMessage,
654
- messages: ReadonlyArray<NewMessage>,
655
- millis: Millis,
656
- ): Effect.Effect<SqliteExecResult, never, Sqlite> =>
657
- Sqlite.pipe(
658
- Effect.map((sqlite) => {
659
- const now = cast(new Date(millis));
660
- return sqlite.exec({
661
- sql: `
662
- insert into
663
- "${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
664
- values
665
- (?, ?, ?, ?)
666
- on conflict do update set
667
- "${message.column}" = ?,
668
- "updatedAt" = ?
669
- `.trim(),
670
- parameters: [message.row, message.value, now, now, message.value, now],
671
- });
672
- }),
673
- Effect.flatMap((insert) =>
674
- Effect.catchSomeDefect(insert, (error) =>
675
- S.is(SqliteNoSuchTableOrColumnError)(error)
676
- ? Option.some(
677
- // If one message fails, we ensure schema for all messages.
678
- ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)),
679
- )
680
- : Option.none(),
681
- ),
682
- ),
683
- );
684
-
685
- const SqliteNoSuchTableOrColumnError = S.Struct({
686
- message: S.Union(
687
- S.String.pipe(S.includes("no such table")),
688
- S.String.pipe(S.includes("no such column")),
689
- S.String.pipe(S.includes("has no column")),
690
- ),
691
- });
692
-
693
- const ensureSchemaByNewMessages = (messages: ReadonlyArray<NewMessage>) =>
694
- Effect.gen(function* () {
695
- const tablesMap = new Map<string, Table>();
696
- messages.forEach((message) => {
697
- const table = tablesMap.get(message.table);
698
- if (table == null) {
699
- tablesMap.set(message.table, {
700
- name: message.table,
701
- columns: [message.column, "createdAt", "updatedAt"],
702
- });
703
- return;
704
- }
705
- if (table.columns.includes(message.column)) return;
706
- tablesMap.set(message.table, {
707
- name: message.table,
708
- columns: table.columns.concat(message.column),
709
- });
710
- });
711
- const tables = Arr.fromIterable(tablesMap.values());
712
- yield* Effect.flatMap(getSchema, ensureSchema({ tables, indexes: [] }));
713
- });
714
-
715
- const writeTimestampAndMerkleTree = (
716
- timestamp: Timestamp,
717
- merkleTree: MerkleTree,
718
- ) =>
719
- Effect.flatMap(Sqlite, (sqlite) =>
720
- sqlite.exec({
721
- ...Sql.updateOwnerTimestampAndMerkleTree,
722
- parameters: [
723
- merkleTreeToString(merkleTree),
724
- timestampToString(timestamp),
725
- ],
726
- }),
727
- );
728
-
729
- const dropAllTables = Effect.gen(function* () {
730
- yield* Effect.logTrace("Db dropAllTables");
731
- const sqlite = yield* Sqlite;
732
- const schema = yield* getSchema;
733
- yield* Effect.forEach(schema.tables, (table) =>
734
- // The dropped table is completely removed from the database schema and
735
- // the disk file. The table can not be recovered.
736
- // All indices and triggers associated with the table are also deleted.
737
- // https://sqlite.org/lang_droptable.html
738
- sqlite.exec({ sql: `drop table "${table.name}"` }),
739
- );
740
- });
741
-
742
- const forkSync = (messages: ReadonlyArray<Message> = []) =>
743
- SyncLock.pipe(
744
- Effect.flatMap((syncLock) => syncLock.tryAcquire),
745
- Effect.matchEffect({
746
- onFailure: () => Effect.void,
747
- onSuccess: () => syncLoop(messages),
748
- }),
749
- Effect.scoped,
750
- Effect.forkDaemon,
751
- );
752
-
753
- const syncLoop = (
754
- messages: ReadonlyArray<Message> = [],
755
- ): Effect.Effect<void, never, Sync | Callbacks | Time | Sqlite | Config> =>
756
- Effect.gen(function* (_) {
757
- const sqlite = yield* Sqlite;
758
- const sync = yield* Sync;
759
- const callbacks = yield* Callbacks;
760
- const time = yield* Time;
761
-
762
- const syncDataRef = yield* readTimestampAndMerkleTree.pipe(
763
- sqlite.transaction("shared"),
764
- Effect.map((a): SyncData => ({ ...a, messages })),
765
- Effect.flatMap(Ref.make),
766
- );
767
-
768
- callbacks.onSyncStateChange({ _tag: "SyncStateIsSyncing" });
769
-
770
- yield* Ref.get(syncDataRef).pipe(
771
- Effect.flatMap(sync.sync),
772
- Effect.flatMap(handleSyncResult),
773
- Effect.repeat({
774
- // TODO: Schedule.jittered(Schedule.exponential("10 millis"))
775
- schedule: Schedule.recurs(50),
776
- until: Option.match({
777
- onNone: () =>
778
- Effect.gen(function* () {
779
- callbacks.onSyncStateChange({
780
- _tag: "SyncStateIsSynced",
781
- time: yield* time.now,
782
- });
783
- return true;
784
- }),
785
- onSome: (syncData) =>
786
- Effect.gen(function* () {
787
- yield* Ref.set(syncDataRef, syncData);
788
- return false;
789
- }),
790
- }),
791
- }),
792
- Effect.catchAllDefect((error) =>
793
- // Db can run in a Web Worker, so we must ensure transferable error.
794
- Effect.fail(makeUnexpectedError(ensureTransferableError(error))),
795
- ),
796
- Effect.catchTag("SyncStateIsNotSynced", (state) =>
797
- Effect.succeed(callbacks.onSyncStateChange(state)),
798
- ),
799
- Effect.catchAll((error) => Effect.succeed(callbacks.onError(error))),
800
- );
801
- });
802
-
803
- const handleSyncResult = (result: SyncResult) =>
804
- Effect.flatMap(Sqlite, (sqlite) =>
805
- Effect.gen(function* (_) {
806
- const { onReceive } = yield* Callbacks;
807
- const current = yield* readTimestampAndMerkleTree;
808
- const nextTimestamp = yield* Effect.reduce(
809
- result.messages,
810
- current.timestamp,
811
- (local, message) =>
812
- receiveTimestamp({
813
- local,
814
- remote: unsafeTimestampFromString(message.timestamp),
815
- }),
816
- );
817
- const nextMerkleTree = yield* applyMessages(
818
- current.merkleTree,
819
- result.messages,
820
- );
821
- if (result.messages.length > 0) {
822
- yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
823
- onReceive();
824
- }
825
- return yield* diffMerkleTrees(result.merkleTree, nextMerkleTree).pipe(
826
- Effect.flatMap((diff) =>
827
- sqlite.exec({
828
- ...Sql.selectMessagesToSync,
829
- parameters: [timestampToString(makeSyncTimestamp(diff))],
830
- }),
831
- ),
832
- Effect.map(
833
- ({ rows }): SyncData => ({
834
- messages: rows as unknown as ReadonlyArray<Message>,
835
- merkleTree: nextMerkleTree,
836
- timestamp: nextTimestamp,
837
- }),
838
- ),
839
- Effect.option,
840
- );
841
- }).pipe(sqlite.transaction("exclusive")),
842
- );
843
-
844
- interface SerializedSqliteQuery {
845
- readonly sql: string;
846
- readonly parameters: (
847
- | null
848
- | string
849
- | number
850
- | Array<number>
851
- | { json: JsonObjectOrArray }
852
- )[];
853
- readonly options?: SqliteQueryOptions;
854
- }
855
-
856
- // We use queries as keys, hence JSON.stringify.
857
- export const serializeQuery = <R extends Row>({
858
- sql,
859
- parameters = [],
860
- options,
861
- }: SqliteQuery): Query<R> => {
862
- const query: SerializedSqliteQuery = {
863
- sql,
864
- parameters: parameters.map((p) =>
865
- Predicate.isUint8Array(p)
866
- ? Arr.fromIterable(p)
867
- : isJsonObjectOrArray(p)
868
- ? { json: p }
869
- : p,
870
- ),
871
- ...(options && { options }),
872
- };
873
- return JSON.stringify(query) as Query<R>;
874
- };
875
-
876
- export const deserializeQuery = <R extends Row>(
877
- query: Query<R>,
878
- ): SqliteQuery => {
879
- const serializedSqliteQuery = JSON.parse(query) as SerializedSqliteQuery;
880
- return {
881
- ...serializedSqliteQuery,
882
- parameters: serializedSqliteQuery.parameters.map((p) =>
883
- Arr.isArray(p)
884
- ? new Uint8Array(p)
885
- : typeof p === "object" && p != null
886
- ? p.json
887
- : p,
888
- ),
889
- };
890
- };
891
-
892
- /**
893
- * Extract {@link Row} from {@link Query} instance.
894
- *
895
- * @example
896
- * const allTodos = evolu.createQuery((db) =>
897
- * db.selectFrom("todo").selectAll(),
898
- * );
899
- * type AllTodosRow = ExtractRow<typeof allTodos>;
900
- */
901
- export type ExtractRow<T extends Query> = T extends Query<infer R> ? R : never;
902
-
903
- // To preserve identity.
904
- const _emptyRows: ReadonlyArray<Row> = [];
905
- export const emptyRows = <R extends Row>(): ReadonlyArray<R> =>
906
- _emptyRows as ReadonlyArray<R>;
907
-
908
- /** An object with rows and row properties. */
909
- export interface QueryResult<R extends Row = Row> {
910
- /** An array containing all the rows returned by the query. */
911
- readonly rows: ReadonlyArray<Readonly<Kysely.Simplify<R>>>;
912
-
913
- /**
914
- * The first row returned by the query, or null if no rows were returned. This
915
- * property is useful for queries that are expected to return a single row.
916
- */
917
- readonly row: Readonly<Kysely.Simplify<R>> | null;
918
- }
919
-
920
- export type Queries<R extends Row = Row> = ReadonlyArray<Query<R>>;
921
-
922
- export type QueryResultsFromQueries<Q extends Queries> = {
923
- [P in keyof Q]: Q[P] extends Query<infer R> ? QueryResult<R> : never;
924
- };
925
-
926
- export type QueryResultsPromisesFromQueries<Q extends Queries> = {
927
- [P in keyof Q]: Q[P] extends Query<infer R> ? Promise<QueryResult<R>> : never;
928
- };
929
-
930
- // To preserve identity.
931
- const queryResultCache = new WeakMap<ReadonlyArray<Row>, QueryResult<Row>>();
932
- export const queryResultFromRows = <R extends Row>(
933
- rows: ReadonlyArray<R>,
934
- ): QueryResult<R> => {
935
- let queryResult = queryResultCache.get(rows);
936
- if (queryResult == null) {
937
- queryResult = { rows, row: rows[0] };
938
- queryResultCache.set(rows, queryResult);
939
- }
940
- return queryResult as QueryResult<R>;
941
- };
942
-
943
- export const notSupportedPlatformWorker: Db = {
944
- init: () =>
945
- Effect.fail<NotSupportedPlatformError>({
946
- _tag: "NotSupportedPlatformError",
947
- }),
948
- loadQueries: () => Effect.succeed([]),
949
- mutate: () => Effect.succeed([]),
950
- resetOwner: () => Effect.void,
951
- restoreOwner: () => Effect.void,
952
- ensureSchema: () => Effect.void,
953
- sync: () => Effect.succeed([]),
954
- exportDatabase: () => Effect.succeed(new Uint8Array()),
955
- };