@evolu/common 4.1.1 → 5.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 (78) hide show
  1. package/README.md +1 -1
  2. package/dist/src/Config.d.ts +27 -30
  3. package/dist/src/Config.d.ts.map +1 -1
  4. package/dist/src/Config.js +32 -5
  5. package/dist/src/Crdt.d.ts +5 -4
  6. package/dist/src/Crdt.d.ts.map +1 -1
  7. package/dist/src/Crdt.js +27 -24
  8. package/dist/src/Crypto.d.ts +16 -10
  9. package/dist/src/Crypto.d.ts.map +1 -1
  10. package/dist/src/Crypto.js +24 -11
  11. package/dist/src/Db.d.ts +58 -78
  12. package/dist/src/Db.d.ts.map +1 -1
  13. package/dist/src/Db.js +368 -170
  14. package/dist/src/Diff.d.ts +9 -3
  15. package/dist/src/Diff.d.ts.map +1 -1
  16. package/dist/src/Diff.js +12 -11
  17. package/dist/src/Error.d.ts +15 -0
  18. package/dist/src/Error.d.ts.map +1 -0
  19. package/dist/src/Error.js +14 -0
  20. package/dist/src/Evolu.d.ts +139 -91
  21. package/dist/src/Evolu.d.ts.map +1 -1
  22. package/dist/src/Evolu.js +267 -239
  23. package/dist/src/Model.d.ts +52 -0
  24. package/dist/src/Model.d.ts.map +1 -1
  25. package/dist/src/Model.js +47 -5
  26. package/dist/src/Owner.d.ts.map +1 -1
  27. package/dist/src/Owner.js +14 -8
  28. package/dist/src/Platform.d.ts +24 -34
  29. package/dist/src/Platform.d.ts.map +1 -1
  30. package/dist/src/Platform.js +9 -31
  31. package/dist/src/Public.d.ts +3 -5
  32. package/dist/src/Public.d.ts.map +1 -1
  33. package/dist/src/Public.js +2 -3
  34. package/dist/src/Sqlite.d.ts +25 -16
  35. package/dist/src/Sqlite.d.ts.map +1 -1
  36. package/dist/src/Sqlite.js +28 -20
  37. package/dist/src/Store.d.ts.map +1 -1
  38. package/dist/src/Sync.d.ts +70 -0
  39. package/dist/src/Sync.d.ts.map +1 -0
  40. package/dist/src/Sync.js +126 -0
  41. package/dist/src/index.d.ts +2 -5
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/index.js +2 -5
  44. package/package.json +12 -10
  45. package/src/Config.ts +77 -36
  46. package/src/Crdt.ts +61 -76
  47. package/src/Crypto.ts +83 -60
  48. package/src/Db.ts +782 -352
  49. package/src/Diff.ts +23 -21
  50. package/src/Error.ts +29 -0
  51. package/src/Evolu.ts +599 -499
  52. package/src/Model.ts +102 -6
  53. package/src/Owner.ts +24 -24
  54. package/src/Platform.ts +33 -81
  55. package/src/Public.ts +3 -5
  56. package/src/Sqlite.ts +81 -44
  57. package/src/Sync.ts +306 -0
  58. package/src/index.ts +2 -5
  59. package/dist/src/DbWorker.d.ts +0 -82
  60. package/dist/src/DbWorker.d.ts.map +0 -1
  61. package/dist/src/DbWorker.js +0 -335
  62. package/dist/src/ErrorStore.d.ts +0 -29
  63. package/dist/src/ErrorStore.d.ts.map +0 -1
  64. package/dist/src/ErrorStore.js +0 -13
  65. package/dist/src/OnCompletes.d.ts +0 -14
  66. package/dist/src/OnCompletes.d.ts.map +0 -1
  67. package/dist/src/OnCompletes.js +0 -25
  68. package/dist/src/SyncWorker.d.ts +0 -80
  69. package/dist/src/SyncWorker.d.ts.map +0 -1
  70. package/dist/src/SyncWorker.js +0 -150
  71. package/dist/src/Types.d.ts +0 -13
  72. package/dist/src/Types.d.ts.map +0 -1
  73. package/dist/src/Types.js +0 -1
  74. package/src/DbWorker.ts +0 -721
  75. package/src/ErrorStore.ts +0 -46
  76. package/src/OnCompletes.ts +0 -51
  77. package/src/SyncWorker.ts +0 -361
  78. package/src/Types.ts +0 -11
package/src/DbWorker.ts DELETED
@@ -1,721 +0,0 @@
1
- import * as Context from "effect/Context";
2
- import * as Effect from "effect/Effect";
3
- import * as Function from "effect/Function";
4
- import { pipe } from "effect/Function";
5
- import * as Layer from "effect/Layer";
6
- import * as Match from "effect/Match";
7
- import * as Option from "effect/Option";
8
- import * as ReadonlyArray from "effect/ReadonlyArray";
9
- import * as ReadonlyRecord from "effect/ReadonlyRecord";
10
- import { Config, ConfigLive } from "./Config.js";
11
- import {
12
- MerkleTree,
13
- Millis,
14
- Time,
15
- TimeLive,
16
- Timestamp,
17
- TimestampCounterOverflowError,
18
- TimestampDriftError,
19
- TimestampError,
20
- TimestampString,
21
- TimestampTimeOutOfRangeError,
22
- diffMerkleTrees,
23
- insertIntoMerkleTree,
24
- makeSyncTimestamp,
25
- merkleTreeToString,
26
- receiveTimestamp,
27
- sendTimestamp,
28
- timestampToString,
29
- unsafeTimestampFromString,
30
- } from "./Crdt.js";
31
- import { Bip39, Mnemonic, NanoIdGenerator } from "./Crypto.js";
32
- import {
33
- Queries,
34
- Query,
35
- RowsStore,
36
- RowsStoreLive,
37
- deserializeQuery,
38
- dropAllTables,
39
- ensureSchema,
40
- lazyInit,
41
- someDefectToNoSuchTableOrColumnError,
42
- transaction,
43
- } from "./Db.js";
44
- import { QueryPatches, makePatches } from "./Diff.js";
45
- import { EvoluError, makeUnexpectedError } from "./ErrorStore.js";
46
- import { Id, cast } from "./Model.js";
47
- import { OnCompleteId } from "./OnCompletes.js";
48
- import { Owner, OwnerId } from "./Owner.js";
49
- import { DbWorkerLock } from "./Platform.js";
50
- import * as Sql from "./Sql.js";
51
- import {
52
- Sqlite,
53
- SqliteQueryPlanRow,
54
- SqliteSchema,
55
- Table,
56
- Value,
57
- drawSqliteQueryPlan as drawExplainQueryPlan,
58
- } from "./Sqlite.js";
59
- import {
60
- Message,
61
- NewMessage,
62
- NewMessageEquivalence,
63
- SyncState,
64
- SyncWorker,
65
- SyncWorkerOutputSyncResponse,
66
- SyncWorkerPostMessage,
67
- } from "./SyncWorker.js";
68
- import { Messaging } from "./Types.js";
69
-
70
- export interface DbWorker extends Messaging<DbWorkerInput, DbWorkerOutput> {}
71
- export const DbWorker = Context.GenericTag<DbWorker>("@services/DbWorker");
72
-
73
- export type DbWorkerInput =
74
- | DbWorkerInputInit
75
- | DbWorkerInputQuery
76
- | DbWorkerInputMutate
77
- | DbWorkerInputSync
78
- | DbWorkerInputReset
79
- | DbWorkerInputEnsureSchema
80
- | SyncWorkerOutputSyncResponse;
81
-
82
- interface DbWorkerInputInit {
83
- readonly _tag: "init";
84
- readonly config: Config;
85
- }
86
-
87
- interface DbWorkerInputQuery {
88
- readonly _tag: "query";
89
- readonly queries: ReadonlyArray.NonEmptyReadonlyArray<Query>;
90
- }
91
-
92
- interface DbWorkerInputMutate {
93
- readonly _tag: "mutate";
94
- readonly mutations: ReadonlyArray.NonEmptyReadonlyArray<Mutation>;
95
- readonly queries: Queries;
96
- }
97
-
98
- interface DbWorkerInputSync {
99
- readonly _tag: "sync";
100
- readonly queries: Queries;
101
- }
102
-
103
- interface DbWorkerInputReset {
104
- readonly _tag: "reset";
105
- readonly mnemonic?: Mnemonic;
106
- }
107
-
108
- interface DbWorkerInputEnsureSchema extends SqliteSchema {
109
- readonly _tag: "ensureSchema";
110
- }
111
-
112
- type DbWorkerOnMessage = DbWorker["onMessage"];
113
-
114
- const DbWorkerOnMessage = Context.GenericTag<DbWorkerOnMessage>(
115
- "@services/DbWorkerOnMessage",
116
- );
117
-
118
- export type DbWorkerOutput =
119
- | DbWorkerOutputOnError
120
- | DbWorkerOutputOnOwner
121
- | DbWorkerOutputOnQuery
122
- | DbWorkerOutputOnReceive
123
- | DbWorkerOutputOnResetOrRestore
124
- | DbWorkerOutputOnSyncState;
125
-
126
- export interface DbWorkerOutputOnError {
127
- readonly _tag: "onError";
128
- readonly error: EvoluError;
129
- }
130
-
131
- export interface DbWorkerOutputOnOwner {
132
- readonly _tag: "onOwner";
133
- readonly owner: Owner;
134
- }
135
-
136
- export interface DbWorkerOutputOnQuery {
137
- readonly _tag: "onQuery";
138
- readonly queriesPatches: ReadonlyArray<QueryPatches>;
139
- readonly onCompleteIds: ReadonlyArray<OnCompleteId>;
140
- }
141
-
142
- interface DbWorkerOutputOnReceive {
143
- readonly _tag: "onReceive";
144
- }
145
-
146
- interface DbWorkerOutputOnResetOrRestore {
147
- readonly _tag: "onResetOrRestore";
148
- }
149
-
150
- interface DbWorkerOutputOnSyncState {
151
- readonly _tag: "onSyncState";
152
- readonly state: SyncState;
153
- }
154
-
155
- export interface Mutation {
156
- readonly table: string;
157
- readonly id: Id;
158
- readonly values: ReadonlyRecord.ReadonlyRecord<
159
- string,
160
- Value | Date | boolean | undefined
161
- >;
162
- readonly isInsert: boolean;
163
- readonly onCompleteId: OnCompleteId | null;
164
- }
165
-
166
- const init = Effect.gen(function* (_) {
167
- const sqlite = yield* _(Sqlite);
168
-
169
- return yield* _(
170
- sqlite.exec(Sql.selectOwner),
171
- Effect.map(
172
- ({ rows: [row] }): Owner => ({
173
- id: row.id as OwnerId,
174
- mnemonic: row.mnemonic as Mnemonic,
175
- encryptionKey: row.encryptionKey as Uint8Array,
176
- }),
177
- ),
178
- someDefectToNoSuchTableOrColumnError,
179
- Effect.catchTag("NoSuchTableOrColumnError", () => lazyInit()),
180
- );
181
- });
182
-
183
- const query = ({
184
- queries,
185
- onCompleteIds = [],
186
- }: {
187
- readonly queries: Queries;
188
- readonly onCompleteIds?: ReadonlyArray<OnCompleteId>;
189
- }): Effect.Effect<void, never, Sqlite | RowsStore | DbWorkerOnMessage> =>
190
- Effect.gen(function* (_) {
191
- const sqlite = yield* _(Sqlite);
192
- const rowsStore = yield* _(RowsStore);
193
- const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
194
-
195
- const queriesRows = yield* _(
196
- ReadonlyArray.dedupe(queries),
197
- Effect.forEach((query) => {
198
- const sqliteQuery = deserializeQuery(query);
199
- return sqlite.exec(sqliteQuery).pipe(
200
- Effect.map((result) => [query, result.rows] as const),
201
- Effect.tap(() => {
202
- if (!sqliteQuery.options?.logExplainQueryPlan) return;
203
- return sqlite
204
- .exec({
205
- ...sqliteQuery,
206
- sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
207
- })
208
- .pipe(
209
- Effect.tap(() => Effect.log("ExplainQueryPlan")),
210
- Effect.tap(({ rows }) => {
211
- // Not using Effect.log because of formating
212
- // eslint-disable-next-line no-console
213
- console.log(
214
- drawExplainQueryPlan(rows as SqliteQueryPlanRow[]),
215
- );
216
- }),
217
- );
218
- }),
219
- );
220
- }),
221
- );
222
-
223
- const previous = rowsStore.getState();
224
- yield* _(rowsStore.setState(new Map([...previous, ...queriesRows])));
225
-
226
- const queriesPatches = queriesRows.map(
227
- ([query, rows]): QueryPatches => ({
228
- query,
229
- patches: makePatches(previous.get(query), rows),
230
- }),
231
- );
232
-
233
- dbWorkerOnMessage({ _tag: "onQuery", queriesPatches, onCompleteIds });
234
- });
235
-
236
- interface TimestampAndMerkleTree {
237
- readonly timestamp: Timestamp;
238
- readonly merkleTree: MerkleTree;
239
- }
240
-
241
- const readTimestampAndMerkleTree = Sqlite.pipe(
242
- Effect.flatMap((sqlite) =>
243
- sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree),
244
- ),
245
- Effect.map(
246
- ({ rows: [{ timestamp, merkleTree }] }): TimestampAndMerkleTree => ({
247
- timestamp: unsafeTimestampFromString(timestamp as TimestampString),
248
- merkleTree: merkleTree as MerkleTree,
249
- }),
250
- ),
251
- );
252
-
253
- export const mutationsToNewMessages = (
254
- mutations: ReadonlyArray<Mutation>,
255
- ): ReadonlyArray<NewMessage> =>
256
- pipe(
257
- mutations,
258
- ReadonlyArray.map(({ id, isInsert, table, values }) =>
259
- pipe(
260
- Object.entries(values),
261
- // Filter values.
262
- ReadonlyArray.filterMap(([key, value]) =>
263
- // The value can be undefined if exactOptionalPropertyTypes isn't true.
264
- // Don't insert nulls because null is the default value.
265
- value === undefined || (isInsert && value == null)
266
- ? Option.none()
267
- : Option.some([key, value] as const),
268
- ),
269
- // Cast values.
270
- ReadonlyArray.map(
271
- ([key, value]) =>
272
- [
273
- key,
274
- typeof value === "boolean"
275
- ? cast(value)
276
- : value instanceof Date
277
- ? cast(value)
278
- : value,
279
- ] as const,
280
- ),
281
- ReadonlyArray.map(
282
- ([key, value]): NewMessage => ({
283
- table,
284
- row: id,
285
- column: key,
286
- value,
287
- }),
288
- ),
289
- ),
290
- ),
291
- (a) => a.flat(),
292
- ReadonlyArray.dedupeWith(NewMessageEquivalence),
293
- );
294
-
295
- const ensureSchemaByNewMessages = (
296
- messages: ReadonlyArray<NewMessage>,
297
- ): Effect.Effect<void, never, Sqlite> =>
298
- Effect.gen(function* (_) {
299
- const tablesMap = new Map<string, Table>();
300
- messages.forEach((message) => {
301
- const table = tablesMap.get(message.table);
302
- if (table == null) {
303
- tablesMap.set(message.table, {
304
- name: message.table,
305
- columns: [message.column, "createdAt", "updatedAt"],
306
- });
307
- return;
308
- }
309
- if (table.columns.includes(message.column)) return;
310
- tablesMap.set(message.table, {
311
- name: message.table,
312
- columns: table.columns.concat(message.column),
313
- });
314
- });
315
- const tables = Array.from(tablesMap.values());
316
- yield* _(ensureSchema({ tables, indexes: [] }));
317
- });
318
-
319
- export const upsertValueIntoTableRowColumn = (
320
- message: NewMessage,
321
- messages: ReadonlyArray<NewMessage>,
322
- millis: Millis,
323
- ): Effect.Effect<void, never, Sqlite> =>
324
- Effect.gen(function* (_) {
325
- const sqlite = yield* _(Sqlite);
326
- const createdAtOrUpdatedAt = cast(new Date(millis));
327
- const insert = sqlite.exec({
328
- sql: `
329
- insert into
330
- "${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
331
- values
332
- (?, ?, ?, ?)
333
- on conflict do update set
334
- "${message.column}" = ?,
335
- "updatedAt" = ?
336
- `.trim(),
337
- parameters: [
338
- message.row,
339
- message.value,
340
- createdAtOrUpdatedAt,
341
- createdAtOrUpdatedAt,
342
- message.value,
343
- createdAtOrUpdatedAt,
344
- ],
345
- });
346
-
347
- yield* _(
348
- insert,
349
- someDefectToNoSuchTableOrColumnError,
350
- Effect.catchTag("NoSuchTableOrColumnError", () =>
351
- // If one message fails, we ensure schema for all messages.
352
- ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)),
353
- ),
354
- );
355
- });
356
-
357
- const applyMessages = ({
358
- merkleTree,
359
- messages,
360
- }: {
361
- merkleTree: MerkleTree;
362
- messages: ReadonlyArray<Message>;
363
- }): Effect.Effect<MerkleTree, never, Sqlite> =>
364
- Effect.gen(function* (_) {
365
- const sqlite = yield* _(Sqlite);
366
-
367
- for (const message of messages) {
368
- const timestamp: TimestampString | null = yield* _(
369
- sqlite.exec({
370
- ...Sql.selectLastTimestampForTableRowColumn,
371
- parameters: [message.table, message.row, message.column, 1],
372
- }),
373
- Effect.map((result) => result.rows),
374
- Effect.flatMap(ReadonlyArray.head),
375
- Effect.map((row) => row.timestamp as TimestampString),
376
- Effect.catchTag("NoSuchElementException", () => Effect.succeed(null)),
377
- );
378
-
379
- if (timestamp == null || timestamp < message.timestamp) {
380
- const { millis } = unsafeTimestampFromString(message.timestamp);
381
- yield* _(upsertValueIntoTableRowColumn(message, messages, millis));
382
- }
383
-
384
- if (timestamp == null || timestamp !== message.timestamp) {
385
- const { changes } = yield* _(
386
- sqlite.exec({
387
- ...Sql.insertIntoMessagesIfNew,
388
- parameters: [
389
- message.timestamp,
390
- message.table,
391
- message.row,
392
- message.column,
393
- message.value,
394
- ],
395
- }),
396
- );
397
- if (changes > 0) {
398
- const timestamp = unsafeTimestampFromString(message.timestamp);
399
- merkleTree = insertIntoMerkleTree(timestamp)(merkleTree);
400
- }
401
- }
402
- }
403
-
404
- return merkleTree;
405
- });
406
-
407
- const writeTimestampAndMerkleTree = ({
408
- timestamp,
409
- merkleTree,
410
- }: TimestampAndMerkleTree): Effect.Effect<void, never, Sqlite> =>
411
- Effect.flatMap(Sqlite, (sqlite) =>
412
- sqlite.exec({
413
- ...Sql.updateOwnerTimestampAndMerkleTree,
414
- parameters: [
415
- merkleTreeToString(merkleTree),
416
- timestampToString(timestamp),
417
- ],
418
- }),
419
- );
420
-
421
- const mutate = ({
422
- mutations,
423
- queries,
424
- }: DbWorkerInputMutate): Effect.Effect<
425
- void,
426
- | TimestampDriftError
427
- | TimestampCounterOverflowError
428
- | TimestampTimeOutOfRangeError,
429
- | Sqlite
430
- | Owner
431
- | Time
432
- | Config
433
- | RowsStore
434
- | DbWorkerOnMessage
435
- | SyncWorkerPostMessage
436
- > =>
437
- Effect.gen(function* (_) {
438
- const [toSync, localOnly] = ReadonlyArray.partition(mutations, (item) =>
439
- item.table.startsWith("_"),
440
- );
441
- const [toUpsert, toDelete] = ReadonlyArray.partition(localOnly, (item) =>
442
- mutationsToNewMessages([item]).some(
443
- (message) => message.column === "isDeleted" && message.value === 1,
444
- ),
445
- ).map(mutationsToNewMessages);
446
-
447
- const time = yield* _(Time);
448
- for (const messageToUpsert of toUpsert) {
449
- const now = yield* _(time.now);
450
- yield* _(upsertValueIntoTableRowColumn(messageToUpsert, toUpsert, now));
451
- }
452
-
453
- const { exec } = yield* _(Sqlite);
454
- yield* _(
455
- Effect.forEach(toDelete, ({ table, row }) =>
456
- exec({
457
- sql: `delete from "${table}" where "id" = ?;`,
458
- parameters: [row],
459
- }),
460
- ),
461
- );
462
-
463
- if (toSync.length > 0) {
464
- let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
465
-
466
- const messages = yield* _(
467
- mutationsToNewMessages(toSync),
468
- Effect.forEach((message) =>
469
- Effect.map(sendTimestamp(timestamp), (nextTimestamp): Message => {
470
- timestamp = nextTimestamp;
471
- return { ...message, timestamp: timestampToString(timestamp) };
472
- }),
473
- ),
474
- );
475
-
476
- merkleTree = yield* _(applyMessages({ merkleTree, messages }));
477
-
478
- yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
479
-
480
- (yield* _(SyncWorkerPostMessage))({
481
- _tag: "sync",
482
- syncUrl: (yield* _(Config)).syncUrl,
483
- messages,
484
- timestamp,
485
- merkleTree,
486
- owner: yield* _(Owner),
487
- syncLoopCount: 0,
488
- });
489
- }
490
-
491
- const onCompleteIds = ReadonlyArray.filterMap(mutations, (item) =>
492
- Option.fromNullable(item.onCompleteId),
493
- );
494
- if (queries.length > 0 || onCompleteIds.length > 0)
495
- yield* _(query({ queries, onCompleteIds }));
496
- });
497
-
498
- const handleSyncResponse = ({
499
- messages,
500
- ...response
501
- }: SyncWorkerOutputSyncResponse): Effect.Effect<
502
- void,
503
- TimestampError,
504
- Sqlite | Time | Config | DbWorkerOnMessage | SyncWorkerPostMessage | Owner
505
- > =>
506
- Effect.gen(function* (_) {
507
- let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
508
-
509
- const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
510
- if (messages.length > 0) {
511
- for (const message of messages)
512
- timestamp = yield* _(
513
- unsafeTimestampFromString(message.timestamp),
514
- (remote) => receiveTimestamp({ local: timestamp, remote }),
515
- );
516
- merkleTree = yield* _(applyMessages({ merkleTree, messages }));
517
- yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
518
- dbWorkerOnMessage({ _tag: "onReceive" });
519
- }
520
-
521
- const diff = diffMerkleTrees(response.merkleTree, merkleTree);
522
-
523
- const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
524
-
525
- if (Option.isNone(diff)) {
526
- syncWorkerPostMessage({ _tag: "syncCompleted" });
527
- dbWorkerOnMessage({
528
- _tag: "onSyncState",
529
- state: {
530
- _tag: "SyncStateIsSynced",
531
- time: yield* _(Time.pipe(Effect.flatMap((time) => time.now))),
532
- },
533
- });
534
- return;
535
- }
536
-
537
- const sqlite = yield* _(Sqlite);
538
- const config = yield* _(Config);
539
- const owner = yield* _(Owner);
540
-
541
- const messagesToSync = yield* _(
542
- sqlite.exec({
543
- ...Sql.selectMessagesToSync,
544
- parameters: [timestampToString(makeSyncTimestamp(diff.value))],
545
- }),
546
- Effect.map(({ rows }) => rows as unknown as ReadonlyArray<Message>),
547
- );
548
-
549
- if (response.syncLoopCount > 100) {
550
- // TODO: dbWorkerOnMessage({ _tag: "onError" });
551
- // eslint-disable-next-line no-console
552
- console.error("Evolu: syncLoopCount > 100");
553
- return;
554
- }
555
-
556
- syncWorkerPostMessage({
557
- _tag: "sync",
558
- syncUrl: config.syncUrl,
559
- messages: messagesToSync,
560
- timestamp,
561
- merkleTree,
562
- owner,
563
- syncLoopCount: response.syncLoopCount + 1,
564
- });
565
- });
566
-
567
- const sync = ({
568
- queries,
569
- }: DbWorkerInputSync): Effect.Effect<
570
- void,
571
- never,
572
- | Sqlite
573
- | Config
574
- | DbWorkerOnMessage
575
- | SyncWorkerPostMessage
576
- | Owner
577
- | RowsStore
578
- > =>
579
- Effect.gen(function* (_) {
580
- if (queries.length > 0) yield* _(query({ queries }));
581
- const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
582
- syncWorkerPostMessage({
583
- _tag: "sync",
584
- ...(yield* _(readTimestampAndMerkleTree)),
585
- syncUrl: (yield* _(Config)).syncUrl,
586
- owner: yield* _(Owner),
587
- messages: [],
588
- syncLoopCount: 0,
589
- });
590
- });
591
-
592
- const reset = (
593
- input: DbWorkerInputReset,
594
- ): Effect.Effect<
595
- void,
596
- never,
597
- Sqlite | Bip39 | NanoIdGenerator | DbWorkerOnMessage
598
- > =>
599
- Effect.gen(function* (_) {
600
- yield* _(dropAllTables);
601
- if (input.mnemonic) yield* _(lazyInit(input.mnemonic));
602
- const onMessage = yield* _(DbWorkerOnMessage);
603
- onMessage({ _tag: "onResetOrRestore" });
604
- });
605
-
606
- export const DbWorkerCommonLive = Layer.effect(
607
- DbWorker,
608
- Effect.gen(function* (_) {
609
- const syncWorker = yield* _(SyncWorker);
610
-
611
- const onError = (error: EvoluError): Effect.Effect<void> =>
612
- Effect.sync(() => {
613
- dbWorker.onMessage({ _tag: "onError", error });
614
- });
615
-
616
- syncWorker.onMessage = (output): void => {
617
- switch (output._tag) {
618
- case "UnexpectedError":
619
- onError(output).pipe(Effect.runSync);
620
- break;
621
- case "SyncWorkerOutputSyncResponse":
622
- dbWorker.postMessage(output);
623
- break;
624
- default:
625
- dbWorker.onMessage({ _tag: "onSyncState", state: output });
626
- }
627
- };
628
-
629
- const runContext = Context.empty().pipe(
630
- Context.add(Sqlite, yield* _(Sqlite)),
631
- Context.add(Bip39, yield* _(Bip39)),
632
- Context.add(NanoIdGenerator, yield* _(NanoIdGenerator)),
633
- Context.add(DbWorkerOnMessage, (output) => {
634
- dbWorker.onMessage(output);
635
- }),
636
- );
637
-
638
- const run = (
639
- effect: Effect.Effect<
640
- void,
641
- EvoluError,
642
- Sqlite | Bip39 | NanoIdGenerator | DbWorkerOnMessage
643
- >,
644
- ): Promise<void> =>
645
- effect.pipe(
646
- Effect.catchAllDefect(makeUnexpectedError),
647
- transaction,
648
- Effect.catchAll(onError),
649
- Effect.provide(runContext),
650
- Effect.runPromise,
651
- );
652
-
653
- type HandleInput = (input: DbWorkerInput) => Promise<void>;
654
-
655
- /** If init fails, we have to allow reset at least. */
656
- const handleInputForInitFail: HandleInput = (input): Promise<void> => {
657
- if (input._tag !== "reset") return Promise.resolve(undefined);
658
- return reset(input).pipe(run);
659
- };
660
-
661
- const makeHandleInputForInitSuccess = (
662
- config: Config,
663
- owner: Owner,
664
- ): HandleInput => {
665
- let skipAllBecauseOfReset = false;
666
-
667
- const layer = Layer.mergeAll(
668
- ConfigLive(config),
669
- Layer.succeed(Owner, owner),
670
- Layer.succeed(SyncWorkerPostMessage, syncWorker.postMessage),
671
- RowsStoreLive,
672
- TimeLive,
673
- ).pipe(Layer.memoize, Effect.scoped, Effect.runSync);
674
-
675
- return (input) => {
676
- if (skipAllBecauseOfReset) return Promise.resolve(undefined);
677
- return Match.value(input).pipe(
678
- Match.tagsExhaustive({
679
- init: () =>
680
- makeUnexpectedError(new Error("init must be called once")),
681
- query,
682
- mutate,
683
- sync,
684
- reset: (input) => {
685
- skipAllBecauseOfReset = true;
686
- return reset(input);
687
- },
688
- ensureSchema,
689
- SyncWorkerOutputSyncResponse: handleSyncResponse,
690
- }),
691
- Effect.provide(layer),
692
- run,
693
- );
694
- };
695
- };
696
-
697
- let handleInput: HandleInput = (input) => {
698
- if (input._tag !== "init")
699
- return run(makeUnexpectedError(new Error("init must be called first")));
700
- handleInput = handleInputForInitFail;
701
- return init.pipe(
702
- Effect.map((owner) => {
703
- dbWorker.onMessage({ _tag: "onOwner", owner });
704
- handleInput = makeHandleInputForInitSuccess(input.config, owner);
705
- }),
706
- run,
707
- );
708
- };
709
-
710
- const dbWorkerLock = yield* _(DbWorkerLock);
711
-
712
- const dbWorker: DbWorker = {
713
- postMessage: (input) => {
714
- dbWorkerLock(() => handleInput(input));
715
- },
716
- onMessage: Function.constVoid,
717
- };
718
-
719
- return dbWorker;
720
- }),
721
- );