@evolu/common 8.0.0-next.2 → 8.0.0-next.4

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 (73) hide show
  1. package/dist/src/Assert.d.ts +5 -5
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +4 -4
  4. package/dist/src/Identicon.js +4 -4
  5. package/dist/src/LockManager.d.ts +74 -0
  6. package/dist/src/LockManager.d.ts.map +1 -0
  7. package/dist/src/LockManager.js +103 -0
  8. package/dist/src/Microtask.d.ts.map +1 -1
  9. package/dist/src/Microtask.js +90 -26
  10. package/dist/src/Object.d.ts.map +1 -1
  11. package/dist/src/RefCount.d.ts.map +1 -1
  12. package/dist/src/RefCount.js +153 -68
  13. package/dist/src/Resource.d.ts +6 -6
  14. package/dist/src/Resource.d.ts.map +1 -1
  15. package/dist/src/Resource.js +211 -162
  16. package/dist/src/Schedule.d.ts +5 -5
  17. package/dist/src/Schedule.d.ts.map +1 -1
  18. package/dist/src/Schedule.js +53 -44
  19. package/dist/src/Sqlite.d.ts +2 -0
  20. package/dist/src/Sqlite.d.ts.map +1 -1
  21. package/dist/src/Sqlite.js +64 -40
  22. package/dist/src/Task.d.ts +6 -33
  23. package/dist/src/Task.d.ts.map +1 -1
  24. package/dist/src/Task.js +213 -204
  25. package/dist/src/Test.d.ts +4 -4
  26. package/dist/src/Time.d.ts +5 -0
  27. package/dist/src/Time.d.ts.map +1 -1
  28. package/dist/src/Time.js +5 -0
  29. package/dist/src/Type.d.ts +7 -0
  30. package/dist/src/Type.d.ts.map +1 -1
  31. package/dist/src/Type.js +7 -0
  32. package/dist/src/WebSocket.d.ts.map +1 -1
  33. package/dist/src/WebSocket.js +7 -6
  34. package/dist/src/Worker.d.ts +42 -8
  35. package/dist/src/Worker.d.ts.map +1 -1
  36. package/dist/src/Worker.js +298 -75
  37. package/dist/src/index.d.ts +1 -0
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +1 -0
  40. package/dist/src/local-first/Db.d.ts +5 -4
  41. package/dist/src/local-first/Db.d.ts.map +1 -1
  42. package/dist/src/local-first/Db.js +44 -38
  43. package/dist/src/local-first/Evolu.d.ts +3 -2
  44. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  45. package/dist/src/local-first/Evolu.js +90 -80
  46. package/dist/src/local-first/Owner.d.ts.map +1 -1
  47. package/dist/src/local-first/Shared.d.ts +65 -54
  48. package/dist/src/local-first/Shared.d.ts.map +1 -1
  49. package/dist/src/local-first/Shared.js +387 -373
  50. package/package.json +1 -1
  51. package/src/Array.ts +8 -8
  52. package/src/Assert.ts +5 -5
  53. package/src/Identicon.ts +4 -4
  54. package/src/LockManager.ts +181 -0
  55. package/src/Microtask.ts +17 -11
  56. package/src/Object.ts +3 -3
  57. package/src/RefCount.ts +25 -21
  58. package/src/Resource.ts +63 -56
  59. package/src/Schedule.ts +70 -48
  60. package/src/Set.ts +4 -4
  61. package/src/Sqlite.ts +41 -30
  62. package/src/Task.ts +77 -120
  63. package/src/Test.ts +4 -4
  64. package/src/Time.ts +7 -0
  65. package/src/Type.ts +9 -0
  66. package/src/WebSocket.ts +7 -6
  67. package/src/Worker.ts +240 -49
  68. package/src/index.ts +1 -0
  69. package/src/local-first/Db.ts +69 -46
  70. package/src/local-first/Evolu.ts +74 -68
  71. package/src/local-first/Owner.ts +1 -1
  72. package/src/local-first/Query.ts +1 -1
  73. package/src/local-first/Shared.ts +472 -464
@@ -20,6 +20,11 @@ import type { ConsoleDep } from "../Console.js";
20
20
  import { createConsole } from "../Console.js";
21
21
  import { createUnknownError } from "../Error.js";
22
22
  import { exhaustiveCheck, todo } from "../Function.js";
23
+ import {
24
+ acquireLeaderLock,
25
+ acquireLeaderLockCallback,
26
+ type LockManagerDep,
27
+ } from "../LockManager.js";
23
28
  import { createMicrotaskBatch } from "../Microtask.js";
24
29
  import type { FlushSyncDep, ReloadAppDep } from "../Platform.js";
25
30
  import { createRefCountByKey } from "../RefCount.js";
@@ -38,7 +43,10 @@ import {
38
43
  UrlSafeString,
39
44
  } from "../Type.js";
40
45
  import type { ExtractType } from "../Types.js";
41
- import type { CreateMessageChannelDep } from "../Worker.js";
46
+ import type {
47
+ CreateBroadcastChannelDep,
48
+ CreateMessageChannelDep,
49
+ } from "../Worker.js";
42
50
  import type { CreateDbWorkerDep } from "./Db.js";
43
51
  import type { EvoluError } from "./Error.js";
44
52
  import type {
@@ -68,13 +76,12 @@ import type {
68
76
  } from "./Schema.js";
69
77
  import { evoluSchemaToSqliteSchema } from "./Schema.js";
70
78
  import type {
71
- DbWorkerInput,
72
- DbWorkerOutput,
79
+ ConsoleEntryOrError,
73
80
  EvoluInput,
74
81
  EvoluOutput,
75
- EvoluTabOutput,
76
82
  SharedWorkerDep,
77
83
  } from "./Shared.js";
84
+ import { consoleEntryOrErrorBroadcastChannelName } from "./Shared.js";
78
85
  import { DbChange } from "./Storage.js";
79
86
  import type { Timestamp } from "./Timestamp.js";
80
87
 
@@ -547,7 +554,9 @@ export type EvoluDeps = EvoluPlatformDeps & EvoluErrorDep & Disposable;
547
554
  * logging and synchronous UI flush.
548
555
  */
549
556
  export type EvoluPlatformDeps = CreateDbWorkerDep &
557
+ CreateBroadcastChannelDep &
550
558
  CreateMessageChannelDep &
559
+ LockManagerDep &
551
560
  ReloadAppDep &
552
561
  SharedWorkerDep &
553
562
  Partial<ConsoleDep> &
@@ -565,17 +574,21 @@ export type EvoluPlatformDeps = CreateDbWorkerDep &
565
574
  * Dispose it only during app shutdown.
566
575
  */
567
576
  export const createEvoluDeps = (deps: EvoluPlatformDeps): EvoluDeps => {
568
- const { createMessageChannel, sharedWorker } = deps;
577
+ const { createBroadcastChannel, sharedWorker } = deps;
569
578
  const console = deps.console ?? createConsole();
570
579
 
571
- const stack = new DisposableStack();
572
- stack.use(sharedWorker);
573
- const evoluError = stack.use(createStore<EvoluError | null>(null));
580
+ using disposer = new DisposableStack();
581
+ disposer.use(sharedWorker);
582
+ const evoluError = disposer.use(createStore<EvoluError | null>(null));
574
583
 
575
- const tabChannel = stack.use(createMessageChannel<EvoluTabOutput>());
576
- tabChannel.port2.onMessage = (message) => {
584
+ const consoleEntryOrErrorBroadcastChannel = disposer.use(
585
+ createBroadcastChannel<ConsoleEntryOrError>(
586
+ consoleEntryOrErrorBroadcastChannelName,
587
+ ),
588
+ );
589
+ consoleEntryOrErrorBroadcastChannel.onMessage = (message) => {
577
590
  switch (message.type) {
578
- case "OnConsoleEntry":
591
+ case "ConsoleEntry":
579
592
  console.write(message.entry);
580
593
  // Fallback channel for unexpected errors without EvoluError typing.
581
594
  if (message.entry.method === "error") {
@@ -583,7 +596,7 @@ export const createEvoluDeps = (deps: EvoluPlatformDeps): EvoluDeps => {
583
596
  }
584
597
  break;
585
598
 
586
- case "OnError":
599
+ case "Error":
587
600
  evoluError.set(message.error);
588
601
  // Keep typed errors visible in logs as operational failures.
589
602
  console.error(message.error);
@@ -594,21 +607,25 @@ export const createEvoluDeps = (deps: EvoluPlatformDeps): EvoluDeps => {
594
607
  }
595
608
  };
596
609
 
597
- sharedWorker.port.postMessage(
598
- {
599
- type: "InitTab",
600
- consoleLevel: console.getLevel(),
601
- port: tabChannel.port1.native,
602
- },
603
- [tabChannel.port1.native],
610
+ sharedWorker.port.onMessage = (message) => {
611
+ deps.createDbWorker().postMessage(message, [message.port]);
612
+ };
613
+
614
+ disposer.use(
615
+ acquireLeaderLockCallback(deps)("tab", () => {
616
+ sharedWorker.port.postMessage({
617
+ type: "AnnounceTabLeader",
618
+ consoleLevel: console.getLevel(),
619
+ });
620
+ }),
604
621
  );
605
622
 
606
- const moved = stack.move();
623
+ const disposables = disposer.move();
607
624
 
608
625
  return {
609
626
  ...deps,
610
627
  evoluError,
611
- [Symbol.dispose]: () => moved.dispose(),
628
+ [Symbol.dispose]: () => disposables.dispose(),
612
629
  };
613
630
  };
614
631
 
@@ -633,12 +650,14 @@ export const createEvolu =
633
650
  const console = run.deps.console.child(name).child("Evolu");
634
651
  console.info("createEvolu");
635
652
 
636
- await using stack = new AsyncDisposableStack();
653
+ await using disposer = new AsyncDisposableStack();
637
654
 
638
- const rowsByQueryMapStore = stack.use(
655
+ const rowsByQueryMapStore = disposer.use(
639
656
  createStore<RowsByQueryMap>(new Map()),
640
657
  );
641
- const subscribedQueriesRefCount = stack.use(createRefCountByKey<Query>());
658
+ const subscribedQueriesRefCount = disposer.use(
659
+ createRefCountByKey<Query>(),
660
+ );
642
661
 
643
662
  interface LoadingPromise {
644
663
  /**
@@ -662,7 +681,7 @@ export const createEvolu =
662
681
  * Settle pending query loads during disposal so awaiting callers and React
663
682
  * `use` thenables do not hang forever during teardown.
664
683
  */
665
- const loadingPromisesByQuery = stack.adopt(
684
+ const loadingPromisesByQuery = disposer.adopt(
666
685
  new Map<Query, LoadingPromise>(),
667
686
  (loadingPromisesByQuery) => {
668
687
  for (const loadingPromise of loadingPromisesByQuery.values()) {
@@ -715,18 +734,18 @@ export const createEvolu =
715
734
  }
716
735
  };
717
736
 
718
- const onMutateCompleteCallbacks = stack.use(createCallbacks(run.deps));
737
+ const onMutateCompleteCallbacks = disposer.use(createCallbacks(run.deps));
719
738
 
720
739
  let exportDatabasePending = null as PromiseWithResolvers<
721
740
  Uint8Array<ArrayBuffer>
722
741
  > | null;
723
742
 
724
- stack.defer(() => {
743
+ disposer.defer(() => {
725
744
  exportDatabasePending?.reject({ type: "EvoluDisposedError" });
726
745
  exportDatabasePending = null;
727
746
  });
728
747
 
729
- const mutateBatch = stack.use(
748
+ const mutateBatch = disposer.use(
730
749
  createMicrotaskBatch<{
731
750
  readonly change: MutationChange;
732
751
  readonly onComplete: (() => void) | undefined;
@@ -747,7 +766,7 @@ export const createEvolu =
747
766
  }),
748
767
  );
749
768
 
750
- const queryBatch = stack.use(
769
+ const queryBatch = disposer.use(
751
770
  createMicrotaskBatch<Query>((queries) => {
752
771
  const dedupedQueries = new Set(queries);
753
772
  assert(
@@ -759,7 +778,7 @@ export const createEvolu =
759
778
  }),
760
779
  );
761
780
 
762
- const useOwnerBatch = stack.use(
781
+ const useOwnerBatch = disposer.use(
763
782
  createMicrotaskBatch<
764
783
  ExtractType<EvoluInput, "UseOwner">["actions"][number]
765
784
  >((actions) => postMessage({ type: "UseOwner", actions })),
@@ -769,11 +788,11 @@ export const createEvolu =
769
788
 
770
789
  // Scope worker/channel wiring and keep only postMessage outside.
771
790
  {
772
- const { createDbWorker, createMessageChannel, sharedWorker } = run.deps;
773
- const dbWorkerChannel = stack.use(
774
- createMessageChannel<DbWorkerOutput, DbWorkerInput>(),
775
- );
776
- const evoluChannel = stack.use(
791
+ const id = createId<"EvoluInstance">(run.deps);
792
+ const { createMessageChannel, sharedWorker } = run.deps;
793
+ disposer.use(await run.orThrow(acquireLeaderLock(id)));
794
+
795
+ const evoluChannel = disposer.use(
777
796
  createMessageChannel<EvoluInput, EvoluOutput>(),
778
797
  );
779
798
 
@@ -858,35 +877,17 @@ export const createEvolu =
858
877
  {
859
878
  type: "CreateEvolu",
860
879
  name,
861
- evoluPort: evoluChannel.port2.native,
862
- dbWorkerPort: dbWorkerChannel.port2.native,
863
- },
864
- [evoluChannel.port2.native, dbWorkerChannel.port2.native],
865
- );
866
-
867
- /**
868
- * No stack.use because Evolu instances don't dispose DbWorker because
869
- * it's SharedWorder responsibility. DbWorker can be used by another tab.
870
- * That's required because SQLite WASM needs a single web worker.
871
- */
872
- createDbWorker().postMessage(
873
- {
874
- type: "Init",
875
- name,
880
+ id,
876
881
  consoleLevel: console.getLevel(),
877
882
  sqliteSchema: evoluSchemaToSqliteSchema(schema, config.indexes),
878
883
  encryptionKey: appOwner.encryptionKey,
879
884
  memoryOnly,
880
- port: dbWorkerChannel.port1.native,
885
+ evoluPort: evoluChannel.port2.native,
881
886
  },
882
- [dbWorkerChannel.port1.native],
887
+ [evoluChannel.port2.native],
883
888
  );
884
889
 
885
890
  postMessage = evoluChannel.port1.postMessage;
886
-
887
- stack.defer(() => {
888
- postMessage({ type: "Dispose" });
889
- });
890
891
  }
891
892
 
892
893
  const createMutation =
@@ -894,7 +895,7 @@ export const createEvolu =
894
895
  kind: Kind,
895
896
  ): Mutation<S, Kind> =>
896
897
  (table, values, options) => {
897
- assertNotDisposed(moved);
898
+ assertNotDisposed(disposables);
898
899
 
899
900
  const {
900
901
  id = createId(run.deps),
@@ -928,7 +929,7 @@ export const createEvolu =
928
929
  const loadQuery = <R extends Row>(
929
930
  query: Query<S, R>,
930
931
  ): Promise<QueryRows<R>> => {
931
- assertNotDisposed(moved);
932
+ assertNotDisposed(disposables);
932
933
 
933
934
  const loadingPromise = loadingPromisesByQuery.get(query);
934
935
  if (loadingPromise) {
@@ -951,7 +952,7 @@ export const createEvolu =
951
952
  };
952
953
 
953
954
  const getQueryRows = <R extends Row>(query: Query<S, R>): QueryRows<R> => {
954
- assertNotDisposed(moved);
955
+ assertNotDisposed(disposables);
955
956
 
956
957
  return (rowsByQueryMapStore.get().get(query) ??
957
958
  emptyArray) as QueryRows<R>;
@@ -961,7 +962,7 @@ export const createEvolu =
961
962
  owner: ReadonlyOwner | Owner,
962
963
  ownerTransports?: NonEmptyReadonlyArray<OwnerTransport>,
963
964
  ): UnuseOwner => {
964
- assertNotDisposed(moved);
965
+ assertNotDisposed(disposables);
965
966
 
966
967
  const effectiveTransports = ownerTransports ?? transports;
967
968
  assertNonEmptyReadonlyArray(
@@ -982,6 +983,7 @@ export const createEvolu =
982
983
  let isUsed = true;
983
984
 
984
985
  return () => {
986
+ if (disposables.disposed) return;
985
987
  assert(isUsed, "UnuseOwner can be called only once.");
986
988
  isUsed = false;
987
989
  useOwnerBatch.push({
@@ -991,10 +993,14 @@ export const createEvolu =
991
993
  };
992
994
  };
993
995
 
994
- const moved = stack.move();
996
+ const disposables = disposer.move();
995
997
 
996
998
  if (isNonEmptyArray(transports)) useOwner(appOwner);
997
999
 
1000
+ if (run.signal.aborted) {
1001
+ await disposables.disposeAsync();
1002
+ }
1003
+
998
1004
  return ok({
999
1005
  name,
1000
1006
  appOwner,
@@ -1012,7 +1018,7 @@ export const createEvolu =
1012
1018
  ],
1013
1019
 
1014
1020
  subscribeQuery: (query) => (listener) => {
1015
- assertNotDisposed(moved);
1021
+ assertNotDisposed(disposables);
1016
1022
 
1017
1023
  subscribedQueriesRefCount.increment(query);
1018
1024
  let isSubscribed = true;
@@ -1036,7 +1042,7 @@ export const createEvolu =
1036
1042
  previousRows = null;
1037
1043
  unsubscribe();
1038
1044
 
1039
- if (moved.disposed) return;
1045
+ if (disposables.disposed) return;
1040
1046
 
1041
1047
  subscribedQueriesRefCount.decrement(query);
1042
1048
  };
@@ -1044,7 +1050,7 @@ export const createEvolu =
1044
1050
  getQueryRows,
1045
1051
 
1046
1052
  exportDatabase: () => {
1047
- assertNotDisposed(moved);
1053
+ assertNotDisposed(disposables);
1048
1054
 
1049
1055
  if (!exportDatabasePending) {
1050
1056
  exportDatabasePending =
@@ -1055,12 +1061,12 @@ export const createEvolu =
1055
1061
  },
1056
1062
 
1057
1063
  deleteDatabase: () => {
1058
- assertNotDisposed(moved);
1064
+ assertNotDisposed(disposables);
1059
1065
  todo();
1060
1066
  },
1061
1067
 
1062
1068
  deleteOwner: (owner) => {
1063
- assertNotDisposed(moved);
1069
+ assertNotDisposed(disposables);
1064
1070
  void owner;
1065
1071
  todo();
1066
1072
  },
@@ -1068,8 +1074,8 @@ export const createEvolu =
1068
1074
  useOwner,
1069
1075
 
1070
1076
  [Symbol.asyncDispose]: () => {
1071
- console.info("dispose");
1072
- return moved.disposeAsync();
1077
+ if (!disposables.disposed) console.info("disposeEvolu");
1078
+ return disposables.disposeAsync();
1073
1079
  },
1074
1080
  } as Evolu<S>);
1075
1081
  };
@@ -100,7 +100,7 @@ export const ownerIdToOwnerIdBytes = (ownerId: OwnerId): OwnerIdBytes =>
100
100
 
101
101
  /** Converts {@link OwnerIdBytes} to {@link OwnerId}. */
102
102
  export const ownerIdBytesToOwnerId = (ownerIdBytes: OwnerIdBytes): OwnerId =>
103
- idBytesToId(ownerIdBytes as IdBytes) as OwnerId;
103
+ idBytesToId(ownerIdBytes) as OwnerId;
104
104
 
105
105
  export const ownerWriteKeyLength = /*#__PURE__*/ NonNegativeInt.orThrow(16);
106
106
 
@@ -400,7 +400,7 @@ const parseObject = (
400
400
  for (const key in obj) {
401
401
  result[key] = parse(obj[key]);
402
402
  }
403
- return result as ReadonlyRecord<string, unknown>;
403
+ return result;
404
404
  };
405
405
 
406
406
  const colName = (col: string): Expression<unknown> =>