@evolu/common 1.0.17 → 2.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 (75) hide show
  1. package/README.md +7 -7
  2. package/dist/src/Config.d.ts +4 -6
  3. package/dist/src/Config.d.ts.map +1 -1
  4. package/dist/src/Config.js +1 -1
  5. package/dist/src/Crdt.d.ts +15 -9
  6. package/dist/src/Crdt.d.ts.map +1 -1
  7. package/dist/src/Crdt.js +15 -14
  8. package/dist/src/Crypto.d.ts +7 -13
  9. package/dist/src/Crypto.d.ts.map +1 -1
  10. package/dist/src/Crypto.js +7 -9
  11. package/dist/src/Db.d.ts +36 -70
  12. package/dist/src/Db.d.ts.map +1 -1
  13. package/dist/src/Db.js +45 -51
  14. package/dist/src/DbWorker.d.ts +13 -14
  15. package/dist/src/DbWorker.d.ts.map +1 -1
  16. package/dist/src/DbWorker.js +56 -57
  17. package/dist/src/Diff.d.ts +3 -1
  18. package/dist/src/Diff.d.ts.map +1 -1
  19. package/dist/src/Diff.js +3 -7
  20. package/dist/src/{Errors.d.ts → ErrorStore.d.ts} +14 -7
  21. package/dist/src/ErrorStore.d.ts.map +1 -0
  22. package/dist/src/{Errors.js → ErrorStore.js} +2 -0
  23. package/dist/src/Evolu.d.ts +316 -58
  24. package/dist/src/Evolu.d.ts.map +1 -1
  25. package/dist/src/Evolu.js +199 -185
  26. package/dist/src/Model.d.ts +47 -57
  27. package/dist/src/Model.d.ts.map +1 -1
  28. package/dist/src/Model.js +30 -35
  29. package/dist/src/OnCompletes.d.ts +11 -0
  30. package/dist/src/OnCompletes.d.ts.map +1 -0
  31. package/dist/src/OnCompletes.js +21 -0
  32. package/dist/src/Owner.d.ts +31 -0
  33. package/dist/src/Owner.d.ts.map +1 -0
  34. package/dist/src/Owner.js +20 -0
  35. package/dist/src/Platform.d.ts +15 -8
  36. package/dist/src/Platform.d.ts.map +1 -1
  37. package/dist/src/Platform.js +5 -5
  38. package/dist/src/Protobuf.d.ts +25 -73
  39. package/dist/src/Protobuf.d.ts.map +1 -1
  40. package/dist/src/Protobuf.js +4 -12
  41. package/dist/src/Public.d.ts +5 -3
  42. package/dist/src/Public.d.ts.map +1 -1
  43. package/dist/src/Public.js +1 -0
  44. package/dist/src/Sqlite.d.ts +8 -11
  45. package/dist/src/Sqlite.d.ts.map +1 -1
  46. package/dist/src/Sqlite.js +7 -7
  47. package/dist/src/Store.d.ts +6 -5
  48. package/dist/src/Store.d.ts.map +1 -1
  49. package/dist/src/Store.js +21 -16
  50. package/dist/src/SyncWorker.d.ts +8 -2
  51. package/dist/src/SyncWorker.d.ts.map +1 -1
  52. package/dist/src/SyncWorker.js +12 -19
  53. package/dist/src/index.d.ts +6 -2
  54. package/dist/src/index.d.ts.map +1 -1
  55. package/dist/src/index.js +6 -2
  56. package/package.json +23 -13
  57. package/src/Config.ts +7 -7
  58. package/src/Crdt.ts +29 -26
  59. package/src/Crypto.ts +14 -19
  60. package/src/Db.ts +113 -166
  61. package/src/DbWorker.ts +110 -140
  62. package/src/Diff.ts +5 -7
  63. package/src/{Errors.ts → ErrorStore.ts} +17 -8
  64. package/src/Evolu.ts +682 -442
  65. package/src/Model.ts +60 -85
  66. package/src/OnCompletes.ts +46 -0
  67. package/src/Owner.ts +65 -0
  68. package/src/Platform.ts +24 -16
  69. package/src/Protobuf.ts +25 -73
  70. package/src/Public.ts +5 -3
  71. package/src/Sqlite.ts +13 -24
  72. package/src/Store.ts +36 -24
  73. package/src/SyncWorker.ts +30 -38
  74. package/src/index.ts +6 -2
  75. package/dist/src/Errors.d.ts.map +0 -1
package/src/DbWorker.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { hexToBytes } from "@noble/ciphers/utils";
2
2
  import {
3
- Brand,
4
3
  Context,
5
4
  Effect,
6
5
  Function,
7
6
  Layer,
7
+ Match,
8
8
  Option,
9
9
  ReadonlyArray,
10
10
  ReadonlyRecord,
11
- Ref,
12
11
  pipe,
13
12
  } from "effect";
14
13
  import { Config, ConfigLive } from "./Config.js";
@@ -33,20 +32,25 @@ import {
33
32
  } from "./Crdt.js";
34
33
  import { Bip39, Mnemonic, NanoId } from "./Crypto.js";
35
34
  import {
36
- Owner,
37
- OwnerId,
35
+ Queries,
36
+ Query,
37
+ RowsStore,
38
+ RowsStoreLive,
38
39
  Table,
39
40
  Tables,
41
+ deserializeQuery,
40
42
  ensureSchema,
41
43
  lazyInit,
42
44
  someDefectToNoSuchTableOrColumnError,
43
45
  transaction,
44
46
  } from "./Db.js";
45
47
  import { QueryPatches, makePatches } from "./Diff.js";
46
- import { EvoluError, UnexpectedError, makeUnexpectedError } from "./Errors.js";
48
+ import { EvoluError, makeUnexpectedError } from "./ErrorStore.js";
47
49
  import { Id, SqliteDate, cast } from "./Model.js";
50
+ import { OnCompleteId } from "./OnCompletes.js";
51
+ import { Owner, OwnerId } from "./Owner.js";
48
52
  import * as Sql from "./Sql.js";
49
- import { Query, Row, Sqlite, Value, queryObjectFromQuery } from "./Sqlite.js";
53
+ import { Sqlite, Value } from "./Sqlite.js";
50
54
  import {
51
55
  Message,
52
56
  NewMessage,
@@ -57,13 +61,12 @@ import {
57
61
  SyncWorkerPostMessage,
58
62
  } from "./SyncWorker.js";
59
63
 
60
- // TODO: Refactor to Effect.
61
64
  export interface DbWorker {
62
65
  readonly postMessage: (input: DbWorkerInput) => void;
63
66
  onMessage: (output: DbWorkerOutput) => void;
64
67
  }
65
68
 
66
- export const DbWorker = Context.Tag<DbWorker>("evolu/DbWorker");
69
+ export const DbWorker = Context.Tag<DbWorker>();
67
70
 
68
71
  export type DbWorkerInput =
69
72
  | DbWorkerInputInit
@@ -77,7 +80,6 @@ export type DbWorkerInput =
77
80
  interface DbWorkerInputInit {
78
81
  readonly _tag: "init";
79
82
  readonly config: Config;
80
- readonly tables: Tables;
81
83
  }
82
84
 
83
85
  interface DbWorkerInputQuery {
@@ -87,13 +89,13 @@ interface DbWorkerInputQuery {
87
89
 
88
90
  interface DbWorkerInputMutate {
89
91
  readonly _tag: "mutate";
90
- readonly items: ReadonlyArray.NonEmptyReadonlyArray<MutateItem>;
91
- readonly queries: ReadonlyArray<Query>;
92
+ readonly mutations: ReadonlyArray.NonEmptyReadonlyArray<Mutation>;
93
+ readonly queries: Queries;
92
94
  }
93
95
 
94
96
  interface DbWorkerInputSync {
95
97
  readonly _tag: "sync";
96
- readonly queries: ReadonlyArray<Query>;
98
+ readonly queries: Queries;
97
99
  }
98
100
 
99
101
  interface DbWorkerInputReset {
@@ -108,9 +110,7 @@ interface DbWorkerInputEnsureSchema {
108
110
 
109
111
  type DbWorkerOnMessage = DbWorker["onMessage"];
110
112
 
111
- const DbWorkerOnMessage = Context.Tag<DbWorkerOnMessage>(
112
- "evolu/DbWorkerOnMessage",
113
- );
113
+ const DbWorkerOnMessage = Context.Tag<DbWorkerOnMessage>();
114
114
 
115
115
  export type DbWorkerOutput =
116
116
  | DbWorkerOutputOnError
@@ -120,12 +120,12 @@ export type DbWorkerOutput =
120
120
  | DbWorkerOutputOnResetOrRestore
121
121
  | DbWorkerOutputOnSyncState;
122
122
 
123
- interface DbWorkerOutputOnError {
123
+ export interface DbWorkerOutputOnError {
124
124
  readonly _tag: "onError";
125
125
  readonly error: EvoluError;
126
126
  }
127
127
 
128
- interface DbWorkerOutputOnOwner {
128
+ export interface DbWorkerOutputOnOwner {
129
129
  readonly _tag: "onOwner";
130
130
  readonly owner: Owner;
131
131
  }
@@ -136,10 +136,6 @@ export interface DbWorkerOutputOnQuery {
136
136
  readonly onCompleteIds: ReadonlyArray<OnCompleteId>;
137
137
  }
138
138
 
139
- export type OnCompleteId = string &
140
- Brand.Brand<"Id"> &
141
- Brand.Brand<"OnComplete">;
142
-
143
139
  interface DbWorkerOutputOnReceive {
144
140
  readonly _tag: "onReceive";
145
141
  }
@@ -153,7 +149,7 @@ interface DbWorkerOutputOnSyncState {
153
149
  readonly state: SyncState;
154
150
  }
155
151
 
156
- export interface MutateItem {
152
+ export interface Mutation {
157
153
  readonly table: string;
158
154
  readonly id: Id;
159
155
  readonly values: ReadonlyRecord.ReadonlyRecord<
@@ -164,54 +160,47 @@ export interface MutateItem {
164
160
  readonly onCompleteId: OnCompleteId | null;
165
161
  }
166
162
 
167
- const init = (
168
- input: DbWorkerInputInit,
169
- ): Effect.Effect<Sqlite | Bip39 | NanoId, never, Owner> =>
170
- Effect.gen(function* (_) {
171
- const sqlite = yield* _(Sqlite);
172
-
173
- return yield* _(
174
- sqlite.exec(Sql.selectOwner),
175
- Effect.map(
176
- ({ rows: [row] }): Owner => ({
177
- id: row.id as OwnerId,
178
- mnemonic: row.mnemonic as Mnemonic,
179
- // expo-sqlite 11.3.2 doesn't support Uint8Array
180
- encryptionKey: hexToBytes(row.encryptionKey as string),
181
- }),
182
- ),
183
- someDefectToNoSuchTableOrColumnError,
184
- Effect.catchTag("NoSuchTableOrColumnError", () => lazyInit()),
185
- Effect.tap(() => ensureSchema(input.tables)),
186
- );
187
- });
188
-
189
- export type RowsCacheMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
190
-
191
- type RowsCacheRef = Ref.Ref<RowsCacheMap>;
192
- const RowsCacheRef = Context.Tag<RowsCacheRef>("evolu/RowsCacheRef");
163
+ const init = Effect.gen(function* (_) {
164
+ const sqlite = yield* _(Sqlite);
165
+
166
+ return yield* _(
167
+ sqlite.exec(Sql.selectOwner),
168
+ Effect.map(
169
+ ({ rows: [row] }): Owner => ({
170
+ id: row.id as OwnerId,
171
+ mnemonic: row.mnemonic as Mnemonic,
172
+ // expo-sqlite 11.3.2 doesn't support Uint8Array
173
+ encryptionKey: hexToBytes(row.encryptionKey as string),
174
+ }),
175
+ ),
176
+ someDefectToNoSuchTableOrColumnError,
177
+ Effect.catchTag("NoSuchTableOrColumnError", () => lazyInit()),
178
+ );
179
+ });
193
180
 
194
181
  const query = ({
195
182
  queries,
196
183
  onCompleteIds = [],
197
184
  }: {
198
- readonly queries: ReadonlyArray<Query>;
185
+ readonly queries: Queries;
199
186
  readonly onCompleteIds?: ReadonlyArray<OnCompleteId>;
200
- }): Effect.Effect<Sqlite | RowsCacheRef | DbWorkerOnMessage, never, void> =>
187
+ }): Effect.Effect<Sqlite | RowsStore | DbWorkerOnMessage, never, void> =>
201
188
  Effect.gen(function* (_) {
202
189
  const sqlite = yield* _(Sqlite);
203
- const rowsCache = yield* _(RowsCacheRef);
190
+ const rowsStore = yield* _(RowsStore);
204
191
  const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
205
192
 
206
193
  const queriesRows = yield* _(
207
- Effect.forEach(queries, (query) =>
194
+ ReadonlyArray.dedupe(queries),
195
+ Effect.forEach((query) =>
208
196
  sqlite
209
- .exec(queryObjectFromQuery(query))
197
+ .exec(deserializeQuery(query))
210
198
  .pipe(Effect.map((result) => [query, result.rows] as const)),
211
199
  ),
212
200
  );
213
- const previous = yield* _(Ref.get(rowsCache));
214
- yield* _(Ref.set(rowsCache, new Map([...previous, ...queriesRows])));
201
+
202
+ const previous = rowsStore.getState();
203
+ yield* _(rowsStore.setState(new Map([...previous, ...queriesRows])));
215
204
 
216
205
  const queriesPatches = queriesRows.map(
217
206
  ([query, rows]): QueryPatches => ({
@@ -241,11 +230,11 @@ const readTimestampAndMerkleTree = Sqlite.pipe(
241
230
  ),
242
231
  );
243
232
 
244
- export const mutateItemsToNewMessages = (
245
- items: ReadonlyArray<MutateItem>,
233
+ export const mutationsToNewMessages = (
234
+ mutations: ReadonlyArray<Mutation>,
246
235
  ): ReadonlyArray<NewMessage> =>
247
236
  pipe(
248
- items,
237
+ mutations,
249
238
  ReadonlyArray.map(({ id, isInsert, now, table, values }) =>
250
239
  pipe(
251
240
  Object.entries(values),
@@ -263,8 +252,8 @@ export const mutateItemsToNewMessages = (
263
252
  typeof value === "boolean"
264
253
  ? cast(value)
265
254
  : value instanceof Date
266
- ? cast(value)
267
- : value,
255
+ ? cast(value)
256
+ : value,
268
257
  ] as const,
269
258
  ),
270
259
  ReadonlyArray.append([isInsert ? "createdAt" : "updatedAt", now]),
@@ -391,14 +380,14 @@ const writeTimestampAndMerkleTree = ({
391
380
  );
392
381
 
393
382
  const mutate = ({
394
- items,
383
+ mutations,
395
384
  queries,
396
385
  }: DbWorkerInputMutate): Effect.Effect<
397
386
  | Sqlite
398
387
  | Owner
399
388
  | Time
400
389
  | Config
401
- | RowsCacheRef
390
+ | RowsStore
402
391
  | DbWorkerOnMessage
403
392
  | SyncWorkerPostMessage,
404
393
  | TimestampDriftError
@@ -407,16 +396,14 @@ const mutate = ({
407
396
  void
408
397
  > =>
409
398
  Effect.gen(function* (_) {
410
- const [toSync, localOnlyItems] = ReadonlyArray.partition(items, (item) =>
399
+ const [toSync, localOnly] = ReadonlyArray.partition(mutations, (item) =>
411
400
  item.table.startsWith("_"),
412
401
  );
413
- const [toUpsert, toDelete] = ReadonlyArray.partition(
414
- localOnlyItems,
415
- (item) =>
416
- mutateItemsToNewMessages([item]).some(
417
- (message) => message.column === "isDeleted" && message.value === 1,
418
- ),
419
- ).map(mutateItemsToNewMessages);
402
+ const [toUpsert, toDelete] = ReadonlyArray.partition(localOnly, (item) =>
403
+ mutationsToNewMessages([item]).some(
404
+ (message) => message.column === "isDeleted" && message.value === 1,
405
+ ),
406
+ ).map(mutationsToNewMessages);
420
407
 
421
408
  yield* _(
422
409
  Effect.forEach(toUpsert, (message) =>
@@ -435,7 +422,7 @@ const mutate = ({
435
422
  let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
436
423
 
437
424
  const messages = yield* _(
438
- mutateItemsToNewMessages(toSync),
425
+ mutationsToNewMessages(toSync),
439
426
  Effect.forEach((message) =>
440
427
  Effect.map(sendTimestamp(timestamp), (nextTimestamp): Message => {
441
428
  timestamp = nextTimestamp;
@@ -459,7 +446,7 @@ const mutate = ({
459
446
  });
460
447
  }
461
448
 
462
- const onCompleteIds = ReadonlyArray.filterMap(items, (item) =>
449
+ const onCompleteIds = ReadonlyArray.filterMap(mutations, (item) =>
463
450
  Option.fromNullable(item.onCompleteId),
464
451
  );
465
452
  if (queries.length > 0 || onCompleteIds.length > 0)
@@ -477,6 +464,7 @@ const handleSyncResponse = ({
477
464
  Effect.gen(function* (_) {
478
465
  let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
479
466
 
467
+ const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
480
468
  if (messages.length > 0) {
481
469
  for (const message of messages)
482
470
  timestamp = yield* _(
@@ -485,11 +473,9 @@ const handleSyncResponse = ({
485
473
  );
486
474
  merkleTree = yield* _(applyMessages({ merkleTree, messages }));
487
475
  yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
476
+ dbWorkerOnMessage({ _tag: "onReceive" });
488
477
  }
489
478
 
490
- const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
491
- dbWorkerOnMessage({ _tag: "onReceive" });
492
-
493
479
  const diff = diffMerkleTrees(response.merkleTree, merkleTree);
494
480
 
495
481
  const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
@@ -506,9 +492,10 @@ const handleSyncResponse = ({
506
492
  return;
507
493
  }
508
494
 
509
- const [sqlite, config, owner] = yield* _(
510
- Effect.all([Sqlite, Config, Owner]),
511
- );
495
+ const sqlite = yield* _(Sqlite);
496
+ const config = yield* _(Config);
497
+ const owner = yield* _(Owner);
498
+
512
499
  const messagesToSync = yield* _(
513
500
  sqlite.exec({
514
501
  sql: Sql.selectMessagesToSync,
@@ -516,6 +503,14 @@ const handleSyncResponse = ({
516
503
  }),
517
504
  Effect.map(({ rows }) => rows as unknown as ReadonlyArray<Message>),
518
505
  );
506
+
507
+ if (response.syncLoopCount > 100) {
508
+ // TODO: dbWorkerOnMessage({ _tag: "onError" });
509
+ // eslint-disable-next-line no-console
510
+ console.error("Evolu: syncLoopCount > 100");
511
+ return;
512
+ }
513
+
519
514
  syncWorkerPostMessage({
520
515
  _tag: "sync",
521
516
  syncUrl: config.syncUrl,
@@ -535,22 +530,17 @@ const sync = ({
535
530
  | DbWorkerOnMessage
536
531
  | SyncWorkerPostMessage
537
532
  | Owner
538
- | RowsCacheRef,
533
+ | RowsStore,
539
534
  never,
540
535
  void
541
536
  > =>
542
537
  Effect.gen(function* (_) {
543
538
  if (queries.length > 0) yield* _(query({ queries }));
544
539
 
545
- const [syncWorkerPostMessage, config, owner, timestampAndMerkleTree] =
546
- yield* _(
547
- Effect.all([
548
- SyncWorkerPostMessage,
549
- Config,
550
- Owner,
551
- readTimestampAndMerkleTree,
552
- ]),
553
- );
540
+ const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
541
+ const config = yield* _(Config);
542
+ const owner = yield* _(Owner);
543
+ const timestampAndMerkleTree = yield* _(readTimestampAndMerkleTree);
554
544
 
555
545
  syncWorkerPostMessage({
556
546
  ...timestampAndMerkleTree,
@@ -593,7 +583,6 @@ export const DbWorkerLive = Layer.effect(
593
583
  DbWorker,
594
584
  Effect.gen(function* (_) {
595
585
  const syncWorker = yield* _(SyncWorker);
596
- const rowsCacheRef = yield* _(Ref.make<RowsCacheMap>(new Map()));
597
586
 
598
587
  const onError = (error: EvoluError): Effect.Effect<never, never, void> =>
599
588
  Effect.sync(() => {
@@ -613,20 +602,27 @@ export const DbWorkerLive = Layer.effect(
613
602
  }
614
603
  };
615
604
 
616
- const context = Context.empty().pipe(
605
+ const runContext = Context.empty().pipe(
617
606
  Context.add(Sqlite, yield* _(Sqlite)),
618
607
  Context.add(Bip39, yield* _(Bip39)),
619
608
  Context.add(NanoId, yield* _(NanoId)),
609
+ Context.add(DbWorkerOnMessage, (output) => {
610
+ dbWorker.onMessage(output);
611
+ }),
620
612
  );
621
613
 
622
614
  const run = (
623
- effect: Effect.Effect<Sqlite | Bip39 | NanoId, EvoluError, void>,
615
+ effect: Effect.Effect<
616
+ Sqlite | Bip39 | NanoId | DbWorkerOnMessage,
617
+ EvoluError,
618
+ void
619
+ >,
624
620
  ): Promise<void> =>
625
621
  effect.pipe(
626
622
  Effect.catchAllDefect(makeUnexpectedError),
627
623
  transaction,
628
624
  Effect.catchAll(onError),
629
- Effect.provide(context),
625
+ Effect.provide(runContext),
630
626
  Effect.runPromise,
631
627
  );
632
628
 
@@ -635,65 +631,39 @@ export const DbWorkerLive = Layer.effect(
635
631
  // Write for reset only in case init fails.
636
632
  const writeForInitFail: Write = (input): Promise<void> => {
637
633
  if (input._tag !== "reset") return Promise.resolve(undefined);
638
- return reset(input).pipe(
639
- Effect.provideService(DbWorkerOnMessage, dbWorker.onMessage),
640
- Effect.provide(context),
641
- run,
642
- );
634
+ return reset(input).pipe(run);
643
635
  };
644
636
 
645
- const makeWriteForInitSuccess = (owner: Owner, config: Config): Write => {
637
+ const makeWriteForInitSuccess = (config: Config, owner: Owner): Write => {
646
638
  let skipAllBecauseOfReset = false;
647
639
 
648
640
  const layer = Layer.mergeAll(
649
641
  ConfigLive(config),
650
- Layer.succeed(DbWorkerOnMessage, dbWorker.onMessage),
651
642
  Layer.succeed(Owner, owner),
652
643
  Layer.succeed(SyncWorkerPostMessage, syncWorker.postMessage),
653
- Layer.succeed(RowsCacheRef, rowsCacheRef),
644
+ RowsStoreLive,
654
645
  TimeLive,
655
646
  );
656
647
 
657
- const mapInputToEffect = (
658
- input: DbWorkerInput,
659
- ): Effect.Effect<
660
- | Sqlite
661
- | RowsCacheRef
662
- | DbWorkerOnMessage
663
- | Owner
664
- | Time
665
- | Config
666
- | SyncWorkerPostMessage
667
- | Bip39
668
- | NanoId,
669
- | UnexpectedError
670
- | TimestampDriftError
671
- | TimestampCounterOverflowError
672
- | TimestampError,
673
- void
674
- > => {
675
- switch (input._tag) {
676
- case "init":
677
- return makeUnexpectedError(new Error("init must be called once"));
678
- case "query":
679
- return query(input);
680
- case "mutate":
681
- return mutate(input);
682
- case "sync":
683
- return sync(input);
684
- case "reset":
685
- skipAllBecauseOfReset = true;
686
- return reset(input);
687
- case "ensureSchema":
688
- return ensureSchema(input.tables);
689
- case "SyncWorkerOutputSyncResponse":
690
- return handleSyncResponse(input);
691
- }
692
- };
693
-
694
648
  return (input) => {
695
649
  if (skipAllBecauseOfReset) return Promise.resolve(undefined);
696
- return mapInputToEffect(input).pipe(Effect.provide(layer), run);
650
+ return Match.value(input).pipe(
651
+ Match.tagsExhaustive({
652
+ init: () =>
653
+ makeUnexpectedError(new Error("init must be called once")),
654
+ query,
655
+ mutate,
656
+ sync,
657
+ reset: (input) => {
658
+ skipAllBecauseOfReset = true;
659
+ return reset(input);
660
+ },
661
+ ensureSchema: ({ tables }) => ensureSchema(tables),
662
+ SyncWorkerOutputSyncResponse: handleSyncResponse,
663
+ }),
664
+ Effect.provide(layer),
665
+ run,
666
+ );
697
667
  };
698
668
  };
699
669
 
@@ -701,10 +671,10 @@ export const DbWorkerLive = Layer.effect(
701
671
  if (input._tag !== "init")
702
672
  return run(makeUnexpectedError(new Error("init must be called first")));
703
673
  write = writeForInitFail;
704
- return init(input).pipe(
674
+ return init.pipe(
705
675
  Effect.map((owner) => {
706
676
  dbWorker.onMessage({ _tag: "onOwner", owner });
707
- write = makeWriteForInitSuccess(owner, input.config);
677
+ write = makeWriteForInitSuccess(input.config, owner);
708
678
  }),
709
679
  run,
710
680
  );
package/src/Diff.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { Predicate } from "effect";
2
- import { Query, Row, Value } from "./Sqlite.js";
1
+ import { Predicate, ReadonlyArray } from "effect";
2
+ import { Query, Row } from "./Db.js";
3
+ import { Value } from "./Sqlite.js";
3
4
 
4
5
  export interface QueryPatches {
5
6
  readonly query: Query;
@@ -22,15 +23,12 @@ export interface ReplaceAtPatch {
22
23
  export const applyPatches =
23
24
  (patches: ReadonlyArray<Patch>) =>
24
25
  (current: ReadonlyArray<Row>): ReadonlyArray<Row> =>
25
- patches.reduce((a, patch) => {
26
+ patches.reduce((next, patch) => {
26
27
  switch (patch.op) {
27
28
  case "replaceAll":
28
29
  return patch.value;
29
30
  case "replaceAt": {
30
- if (a === undefined) return a;
31
- const next = [...a];
32
- next[patch.index] = patch.value;
33
- return next;
31
+ return ReadonlyArray.replace(next, patch.index, patch.value);
34
32
  }
35
33
  }
36
34
  }, current);
@@ -1,18 +1,17 @@
1
1
  import { Effect } from "effect";
2
2
  import { TimestampError } from "./Crdt.js";
3
+ import { makeStore } from "./Store.js";
3
4
 
5
+ export const makeErrorStore = makeStore<EvoluError | null>(null);
6
+
7
+ /** The EvoluError type is used to represent errors that can occur in Evolu. */
4
8
  export type EvoluError = UnexpectedError | TimestampError;
5
9
 
6
10
  /**
7
- * We can't use the whole error because of WebWorker postMessage DataCloneError
8
- * in Safari and Firefox.
9
- * https://discord.com/channels/795981131316985866/795983589644304396/1096736473396564079
11
+ * The UnexpectedError interface is designed to represent errors that can occur
12
+ * unexpectedly anywhere, even in third-party libraries, because Evolu uses
13
+ * Effect to track all errors.
10
14
  */
11
- interface TransferableError {
12
- readonly message: string;
13
- readonly stack: string | undefined;
14
- }
15
-
16
15
  export interface UnexpectedError {
17
16
  readonly _tag: "UnexpectedError";
18
17
  readonly error: TransferableError;
@@ -31,3 +30,13 @@ export const makeUnexpectedError = (
31
30
  },
32
31
  });
33
32
  };
33
+
34
+ /**
35
+ * We can't use the whole error because of WebWorker postMessage DataCloneError
36
+ * in Safari and Firefox. TODO:
37
+ * https://discord.com/channels/795981131316985866/795983589644304396/1096736473396564079
38
+ */
39
+ interface TransferableError {
40
+ readonly message: string;
41
+ readonly stack: string | undefined;
42
+ }