@evolu/react-native 8.0.0 → 9.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.
@@ -1,7 +1,6 @@
1
- import { AppState, Bip39, DbWorkerLock, SyncLock } from "@evolu/common";
1
+ import { AppState, Bip39, SyncLock } from "@evolu/common";
2
2
  import * as Layer from "effect/Layer";
3
- export declare const SyncLockLive: Layer.Layer<SyncLock, never, never>;
4
- export declare const DbWorkerLockLive: Layer.Layer<DbWorkerLock, never, never>;
5
3
  export declare const AppStateLive: Layer.Layer<AppState, never, never>;
4
+ export declare const SyncLockLive: Layer.Layer<SyncLock, never, never>;
6
5
  export declare const Bip39Live: Layer.Layer<Bip39, never, never>;
7
6
  //# sourceMappingURL=PlatformLive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlatformLive.d.ts","sourceRoot":"","sources":["../src/PlatformLive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,EACL,YAAY,EAGZ,QAAQ,EACT,MAAM,eAAe,CAAC;AASvB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,eAAO,MAAM,YAAY,qCAexB,CAAC;AAEF,eAAO,MAAM,gBAAgB,yCAQ5B,CAAC;AAEF,eAAO,MAAM,YAAY,qCA4BxB,CAAC;AAEF,eAAO,MAAM,SAAS,kCAYrB,CAAC"}
1
+ {"version":3,"file":"PlatformLive.d.ts","sourceRoot":"","sources":["../src/PlatformLive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,EAEL,QAAQ,EAIT,MAAM,eAAe,CAAC;AASvB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,eAAO,MAAM,YAAY,qCAmCxB,CAAC;AAEF,eAAO,MAAM,YAAY,qCA2BxB,CAAC;AAEF,eAAO,MAAM,SAAS,kCAUrB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { AppState, Bip39, DbWorkerLock, SyncLock, } from "@evolu/common";
1
+ import { AppState, Bip39, SyncLock, SyncLockAlreadySyncingError, validateMnemonicToEffect, } from "@evolu/common";
2
2
  import NetInfo from "@react-native-community/netinfo";
3
3
  import { generateMnemonic, mnemonicToSeed, validateMnemonic, } from "@scure/bip39";
4
4
  import { wordlist } from "@scure/bip39/wordlists/english";
@@ -6,31 +6,12 @@ import * as Effect from "effect/Effect";
6
6
  import * as Layer from "effect/Layer";
7
7
  import { reloadAsync } from "expo-updates";
8
8
  import { DevSettings, AppState as ReactNativeAppState } from "react-native";
9
- export const SyncLockLive = Layer.effect(SyncLock, Effect.sync(() => {
10
- let hasSyncLock = false;
11
- return SyncLock.of({
12
- acquire: Effect.sync(() => {
13
- if (hasSyncLock)
14
- return false;
15
- hasSyncLock = true;
16
- return true;
17
- }),
18
- release: Effect.sync(() => {
19
- hasSyncLock = false;
20
- }),
21
- });
22
- }));
23
- export const DbWorkerLockLive = Layer.effect(DbWorkerLock, Effect.sync(() => {
24
- let queue = Promise.resolve(undefined);
25
- return DbWorkerLock.of((callback) => {
26
- queue = queue.then(callback);
27
- });
28
- }));
29
9
  export const AppStateLive = Layer.succeed(AppState, AppState.of({
30
- init: ({ onRequestSync }) => {
10
+ init: ({ onRequestSync }) => Effect.sync(() => {
31
11
  let appStateStatus = ReactNativeAppState.currentState;
32
12
  ReactNativeAppState.addEventListener("change", (current) => {
33
- if (appStateStatus.match(/inactive|background/) && current === "active")
13
+ if (appStateStatus.match(/inactive|background/) &&
14
+ current === "active")
34
15
  onRequestSync();
35
16
  appStateStatus = current;
36
17
  });
@@ -42,18 +23,41 @@ export const AppStateLive = Layer.succeed(AppState, AppState.of({
42
23
  onRequestSync();
43
24
  netInfoState = current;
44
25
  });
45
- },
46
- reset: Effect.sync(() => {
47
- if (process.env.NODE_ENV === "development")
48
- DevSettings.reload();
49
- else
50
- reloadAsync();
26
+ const reset = process.env.NODE_ENV === "development"
27
+ ? Effect.sync(() => {
28
+ DevSettings.reload();
29
+ })
30
+ : Effect.promise(() => reloadAsync());
31
+ return { reset };
51
32
  }),
52
33
  }));
34
+ export const SyncLockLive = Layer.effect(SyncLock, Effect.sync(() => {
35
+ let hasSyncLock = false;
36
+ return SyncLock.of({
37
+ tryAcquire: Effect.gen(function* () {
38
+ yield* Effect.logTrace("SyncLock tryAcquire");
39
+ const acquire = Effect.gen(function* () {
40
+ if (hasSyncLock) {
41
+ yield* Effect.logTrace("SyncLock not acquired");
42
+ yield* Effect.fail(new SyncLockAlreadySyncingError());
43
+ }
44
+ yield* Effect.logTrace("SyncLock acquired");
45
+ hasSyncLock = true;
46
+ const syncLockRelease = {
47
+ release: Effect.gen(function* () {
48
+ yield* Effect.logTrace("SyncLock released");
49
+ hasSyncLock = false;
50
+ }),
51
+ };
52
+ return syncLockRelease;
53
+ });
54
+ const release = ({ release }) => release;
55
+ return yield* Effect.acquireRelease(acquire, release);
56
+ }),
57
+ });
58
+ }));
53
59
  export const Bip39Live = Layer.succeed(Bip39, Bip39.of({
54
60
  make: Effect.sync(() => generateMnemonic(wordlist, 128)),
55
61
  toSeed: (mnemonic) => Effect.promise(() => mnemonicToSeed(mnemonic)),
56
- parse: (mnemonic) => validateMnemonic(mnemonic, wordlist)
57
- ? Effect.succeed(mnemonic)
58
- : Effect.fail({ _tag: "InvalidMnemonicError" }),
62
+ parse: (mnemonic) => validateMnemonicToEffect(validateMnemonic)(mnemonic, wordlist),
59
63
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"SqliteLive.d.ts","sourceRoot":"","sources":["../src/SqliteLive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EAMP,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,eAAO,MAAM,UAAU,oCA0BtB,CAAC"}
1
+ {"version":3,"file":"SqliteLive.d.ts","sourceRoot":"","sources":["../src/SqliteLive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,EAKP,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,eAAO,MAAM,UAAU,oCA0BtB,CAAC"}
@@ -1,20 +1,22 @@
1
- import { Config, Sqlite, isSqlMutation, maybeLogSqliteQueryExecutionTime, maybeParseJson, valuesToSqliteValues, } from "@evolu/common";
1
+ import { Config, Sqlite, isSqlMutation, maybeLogSqliteQueryExecutionTime, valuesToSqliteValues, } from "@evolu/common";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
4
  import * as ExpoSQLite from "expo-sqlite/next";
5
- export const SqliteLive = Layer.effect(Sqlite, Effect.gen(function* (_) {
6
- const config = yield* _(Config);
5
+ export const SqliteLive = Layer.effect(Sqlite, Effect.gen(function* () {
6
+ const config = yield* Config;
7
7
  const db = ExpoSQLite.openDatabaseSync(`evolu1-${config.name}.db`);
8
+ const mutex = yield* Effect.makeSemaphore(1);
8
9
  return Sqlite.of({
9
- exec: (query) => Effect.gen(function* (_) {
10
+ exec: (query) => Effect.gen(function* () {
10
11
  const parameters = valuesToSqliteValues(query.parameters || []);
11
12
  if (!isSqlMutation(query.sql)) {
12
- const rows = (yield* _(Effect.promise(() => db.getAllAsync(query.sql, parameters)), maybeLogSqliteQueryExecutionTime(query)));
13
- maybeParseJson(rows);
13
+ const rows = (yield* Effect.promise(() => db.getAllAsync(query.sql, parameters)).pipe(maybeLogSqliteQueryExecutionTime(query)));
14
14
  return { rows, changes: 0 };
15
15
  }
16
- const { changes } = yield* _(Effect.promise(() => db.runAsync(query.sql, parameters)));
16
+ const { changes } = yield* Effect.promise(() => db.runAsync(query.sql, parameters));
17
17
  return { rows: [], changes };
18
18
  }),
19
+ // RN doesn't need the "last" mode because there are no tabs.
20
+ transaction: () => mutex.withPermits(1),
19
21
  });
20
22
  }));
package/dist/index.d.ts CHANGED
@@ -1,39 +1,40 @@
1
- import { InvalidMnemonicError, Mnemonic } from "@evolu/common";
1
+ import { EvoluFactory, InvalidMnemonicError, Mnemonic } from "@evolu/common";
2
2
  import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
3
4
  export * from "@evolu/common/public";
4
5
  /** Parse a string to {@link Mnemonic}. */
5
6
  export declare const parseMnemonic: (mnemonic: string) => Effect.Effect<Mnemonic, InvalidMnemonicError>;
7
+ export declare const EvoluFactoryReactNative: Layer.Layer<EvoluFactory, never, never>;
8
+ export declare const
6
9
  /**
7
- * Create Evolu for React Native.
10
+ * Create Evolu from the database schema.
8
11
  *
9
12
  * Tables with a name prefixed with `_` are local-only, which means they are
10
13
  * never synced. It's useful for device-specific or temporal data.
11
14
  *
12
15
  * @example
13
16
  * import * as S from "@effect/schema/Schema";
14
- * import {
15
- * NonEmptyString1000,
16
- * createEvolu,
17
- * id,
18
- * } from "@evolu/react-native";
17
+ * import * as E from "@evolu/react-native";
19
18
  *
20
- * const TodoId = id("Todo");
19
+ * const TodoId = E.id("Todo");
21
20
  * type TodoId = S.Schema.Type<typeof TodoId>;
22
21
  *
23
- * const TodoTable = table({
22
+ * const TodoTable = E.table({
24
23
  * id: TodoId,
25
- * title: NonEmptyString1000,
24
+ * title: E.NonEmptyString1000,
26
25
  * });
27
26
  * type TodoTable = S.Schema.Type<typeof TodoTable>;
28
27
  *
29
- * const Database = database({
30
- * // _todo is local-only table
28
+ * const Database = E.database({
31
29
  * todo: TodoTable,
30
+ *
31
+ * // Prefix `_` makes the table local-only (it will not sync)
32
+ * _todo: TodoTable,
32
33
  * });
33
34
  * type Database = S.Schema.Type<typeof Database>;
34
35
  *
35
- * const evolu = createEvolu(Database);
36
+ * const evolu = E.createEvolu(Database);
36
37
  */
37
- export declare const createEvolu: <From, To extends import("@evolu/common").DatabaseSchema>(schema: import("@effect/schema/Schema").Schema<To, From, never>, config?: Partial<import("@evolu/common").Config> | undefined) => import("@evolu/common").Evolu<To>;
38
+ createEvolu: <T extends import("@evolu/common").EvoluSchema, I>(schema: import("@effect/schema/Schema").Schema<T, I, never>, config?: Partial<import("@evolu/common").EvoluConfig<T>> | undefined) => import("@evolu/common").Evolu<T>;
38
39
  export * from "@evolu/common-react";
39
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,oBAAoB,EACpB,QAAQ,EAKT,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAUxC,cAAc,sBAAsB,CAAC;AAErC,0CAA0C;AAC1C,eAAO,MAAM,aAAa,EAAE,CAC1B,QAAQ,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,oBAAoB,CAG1C,CAAC;AAER;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,WAAW,+NAgBvB,CAAC;AAEF,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,oBAAoB,EACpB,QAAQ,EAST,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAStC,cAAc,sBAAsB,CAAC;AAErC,0CAA0C;AAC1C,eAAO,MAAM,aAAa,EAAE,CAC1B,QAAQ,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,oBAAoB,CAG1C,CAAC;AAUR,eAAO,MAAM,uBAAuB,yCAuBnC,CAAC;AAKF,eAAO;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,WAAW,2NACiE,CAAC;AAE/E,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,41 +1,56 @@
1
- import { Bip39, DbWorkerCommonLive, EvoluCommonLive, FetchLive, FlushSyncDefaultLive, NanoIdGeneratorLive, SecretBoxLive, SyncWorkerCommonLive, makeCreateEvolu, } from "@evolu/common";
1
+ import { Bip39, DbFactory, EvoluFactory, SecretBox, Sqlite, SqliteFactory, SyncFactory, Time, createDb, createNanoIdGeneratorLive, createSync, } from "@evolu/common";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
- import { AppStateLive, Bip39Live, DbWorkerLockLive, SyncLockLive, } from "./PlatformLive.js";
4
+ // @ts-expect-error https://github.com/ai/nanoid/issues/468
5
+ import { customAlphabet, nanoid } from "nanoid/index.browser.js";
6
+ import { AppStateLive, Bip39Live, SyncLockLive } from "./PlatformLive.js";
5
7
  import { SqliteLive } from "./SqliteLive.js";
8
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
9
+ const NanoIdGeneratorLive = createNanoIdGeneratorLive(customAlphabet, nanoid);
6
10
  export * from "@evolu/common/public";
7
11
  /** Parse a string to {@link Mnemonic}. */
8
12
  export const parseMnemonic = Bip39.pipe(Effect.provide(Bip39Live), Effect.runSync).parse;
13
+ const SyncFactoryLive = Layer.succeed(SyncFactory, {
14
+ createSync: Effect.provide(createSync, SecretBox.Live),
15
+ });
16
+ const SqliteFactoryLive = Layer.succeed(SqliteFactory, {
17
+ createSqlite: Sqlite.pipe(Effect.provide(SqliteLive)),
18
+ });
19
+ export const EvoluFactoryReactNative = Layer.provide(EvoluFactory.Common, Layer.mergeAll(Layer.succeed(DbFactory, {
20
+ createDb: createDb.pipe(Effect.provide(Layer.mergeAll(Bip39Live, SqliteFactory.Common.pipe(Layer.provide(SqliteFactoryLive), Layer.provide(NanoIdGeneratorLive)), NanoIdGeneratorLive, Time.Live, SyncFactoryLive, SyncLockLive))),
21
+ }), NanoIdGeneratorLive, AppStateLive));
22
+ // JSDoc doesn't support destructured parameters, so we must copy-paste
23
+ // createEvolu docs from `evolu-common/src/Evolu.ts`.
24
+ // https://github.com/microsoft/TypeScript/issues/11859
25
+ export const {
9
26
  /**
10
- * Create Evolu for React Native.
27
+ * Create Evolu from the database schema.
11
28
  *
12
29
  * Tables with a name prefixed with `_` are local-only, which means they are
13
30
  * never synced. It's useful for device-specific or temporal data.
14
31
  *
15
32
  * @example
16
33
  * import * as S from "@effect/schema/Schema";
17
- * import {
18
- * NonEmptyString1000,
19
- * createEvolu,
20
- * id,
21
- * } from "@evolu/react-native";
34
+ * import * as E from "@evolu/react-native";
22
35
  *
23
- * const TodoId = id("Todo");
36
+ * const TodoId = E.id("Todo");
24
37
  * type TodoId = S.Schema.Type<typeof TodoId>;
25
38
  *
26
- * const TodoTable = table({
39
+ * const TodoTable = E.table({
27
40
  * id: TodoId,
28
- * title: NonEmptyString1000,
41
+ * title: E.NonEmptyString1000,
29
42
  * });
30
43
  * type TodoTable = S.Schema.Type<typeof TodoTable>;
31
44
  *
32
- * const Database = database({
33
- * // _todo is local-only table
45
+ * const Database = E.database({
34
46
  * todo: TodoTable,
47
+ *
48
+ * // Prefix `_` makes the table local-only (it will not sync)
49
+ * _todo: TodoTable,
35
50
  * });
36
51
  * type Database = S.Schema.Type<typeof Database>;
37
52
  *
38
- * const evolu = createEvolu(Database);
53
+ * const evolu = E.createEvolu(Database);
39
54
  */
40
- export const createEvolu = makeCreateEvolu(EvoluCommonLive.pipe(Layer.provide(Layer.mergeAll(FlushSyncDefaultLive, AppStateLive, DbWorkerCommonLive)), Layer.provide(Layer.mergeAll(Bip39Live, NanoIdGeneratorLive, SqliteLive, SyncWorkerCommonLive, DbWorkerLockLive)), Layer.provide(Layer.mergeAll(SecretBoxLive, SyncLockLive, FetchLive))));
55
+ createEvolu, } = EvoluFactory.pipe(Effect.provide(EvoluFactoryReactNative), Effect.runSync);
41
56
  export * from "@evolu/common-react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/react-native",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "description": "Evolu for React Native",
5
5
  "keywords": [
6
6
  "evolu",
@@ -31,25 +31,25 @@
31
31
  "README.md"
32
32
  ],
33
33
  "dependencies": {
34
- "expo-updates": "^0.24.11"
34
+ "expo-updates": "^0.24.12"
35
35
  },
36
36
  "devDependencies": {
37
37
  "eslint": "^8.57.0",
38
- "expo": "^50.0.14",
38
+ "expo": "^50.0.17",
39
39
  "expo-sqlite": "~13.4.0",
40
40
  "react-native": "0.73.6",
41
- "typescript": "^5.4.3",
42
- "vitest": "^1.4.0",
43
- "@evolu/common": "4.1.0",
44
- "@evolu/common-react": "7.0.0",
41
+ "typescript": "^5.4.5",
42
+ "vitest": "^1.5.2",
43
+ "@evolu/common": "5.0.0",
44
+ "@evolu/common-react": "8.0.0",
45
45
  "@evolu/tsconfig": "0.0.2",
46
46
  "eslint-config-evolu": "1.0.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "@evolu/common-react": "^7.0.0",
49
+ "@evolu/common-react": "^8.0.0",
50
50
  "expo": "^50.0.14",
51
51
  "expo-sqlite": "~13.4.0",
52
- "react-native": "0.73.6"
52
+ "react-native": "^0.73.6"
53
53
  },
54
54
  "publishConfig": {
55
55
  "access": "public"
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  AppState,
3
3
  Bip39,
4
- DbWorkerLock,
5
- InvalidMnemonicError,
6
4
  Mnemonic,
7
5
  SyncLock,
6
+ SyncLockAlreadySyncingError,
7
+ SyncLockRelease,
8
+ validateMnemonicToEffect,
8
9
  } from "@evolu/common";
9
10
  import NetInfo, { NetInfoState } from "@react-native-community/netinfo";
10
11
  import {
@@ -18,63 +19,72 @@ import * as Layer from "effect/Layer";
18
19
  import { reloadAsync } from "expo-updates";
19
20
  import { DevSettings, AppState as ReactNativeAppState } from "react-native";
20
21
 
22
+ export const AppStateLive = Layer.succeed(
23
+ AppState,
24
+ AppState.of({
25
+ init: ({ onRequestSync }) =>
26
+ Effect.sync(() => {
27
+ let appStateStatus = ReactNativeAppState.currentState;
28
+ ReactNativeAppState.addEventListener("change", (current) => {
29
+ if (
30
+ appStateStatus.match(/inactive|background/) &&
31
+ current === "active"
32
+ )
33
+ onRequestSync();
34
+ appStateStatus = current;
35
+ });
36
+
37
+ let netInfoState: NetInfoState | null = null;
38
+ NetInfo.addEventListener((current) => {
39
+ if (
40
+ netInfoState?.isInternetReachable === false &&
41
+ current.isConnected &&
42
+ current.isInternetReachable
43
+ )
44
+ onRequestSync();
45
+ netInfoState = current;
46
+ });
47
+
48
+ const reset =
49
+ process.env.NODE_ENV === "development"
50
+ ? Effect.sync(() => {
51
+ DevSettings.reload();
52
+ })
53
+ : Effect.promise(() => reloadAsync());
54
+ return { reset };
55
+ }),
56
+ }),
57
+ );
58
+
21
59
  export const SyncLockLive = Layer.effect(
22
60
  SyncLock,
23
61
  Effect.sync(() => {
24
62
  let hasSyncLock = false;
25
63
  return SyncLock.of({
26
- acquire: Effect.sync(() => {
27
- if (hasSyncLock) return false;
28
- hasSyncLock = true;
29
- return true;
30
- }),
31
- release: Effect.sync(() => {
32
- hasSyncLock = false;
64
+ tryAcquire: Effect.gen(function* () {
65
+ yield* Effect.logTrace("SyncLock tryAcquire");
66
+ const acquire = Effect.gen(function* () {
67
+ if (hasSyncLock) {
68
+ yield* Effect.logTrace("SyncLock not acquired");
69
+ yield* Effect.fail(new SyncLockAlreadySyncingError());
70
+ }
71
+ yield* Effect.logTrace("SyncLock acquired");
72
+ hasSyncLock = true;
73
+ const syncLockRelease: SyncLockRelease = {
74
+ release: Effect.gen(function* () {
75
+ yield* Effect.logTrace("SyncLock released");
76
+ hasSyncLock = false;
77
+ }),
78
+ };
79
+ return syncLockRelease;
80
+ });
81
+ const release = ({ release }: SyncLockRelease) => release;
82
+ return yield* Effect.acquireRelease(acquire, release);
33
83
  }),
34
84
  });
35
85
  }),
36
86
  );
37
87
 
38
- export const DbWorkerLockLive = Layer.effect(
39
- DbWorkerLock,
40
- Effect.sync(() => {
41
- let queue: Promise<void> = Promise.resolve(undefined);
42
- return DbWorkerLock.of((callback) => {
43
- queue = queue.then(callback);
44
- });
45
- }),
46
- );
47
-
48
- export const AppStateLive = Layer.succeed(
49
- AppState,
50
- AppState.of({
51
- init: ({ onRequestSync }) => {
52
- let appStateStatus = ReactNativeAppState.currentState;
53
- ReactNativeAppState.addEventListener("change", (current): void => {
54
- if (appStateStatus.match(/inactive|background/) && current === "active")
55
- onRequestSync();
56
- appStateStatus = current;
57
- });
58
-
59
- let netInfoState: NetInfoState | null = null;
60
- NetInfo.addEventListener((current) => {
61
- if (
62
- netInfoState?.isInternetReachable === false &&
63
- current.isConnected &&
64
- current.isInternetReachable
65
- )
66
- onRequestSync();
67
- netInfoState = current;
68
- });
69
- },
70
-
71
- reset: Effect.sync(() => {
72
- if (process.env.NODE_ENV === "development") DevSettings.reload();
73
- else reloadAsync();
74
- }),
75
- }),
76
- );
77
-
78
88
  export const Bip39Live = Layer.succeed(
79
89
  Bip39,
80
90
  Bip39.of({
@@ -83,8 +93,6 @@ export const Bip39Live = Layer.succeed(
83
93
  toSeed: (mnemonic) => Effect.promise(() => mnemonicToSeed(mnemonic)),
84
94
 
85
95
  parse: (mnemonic) =>
86
- validateMnemonic(mnemonic, wordlist)
87
- ? Effect.succeed(mnemonic as Mnemonic)
88
- : Effect.fail<InvalidMnemonicError>({ _tag: "InvalidMnemonicError" }),
96
+ validateMnemonicToEffect(validateMnemonic)(mnemonic, wordlist),
89
97
  }),
90
98
  );
package/src/SqliteLive.ts CHANGED
@@ -4,7 +4,6 @@ import {
4
4
  SqliteRow,
5
5
  isSqlMutation,
6
6
  maybeLogSqliteQueryExecutionTime,
7
- maybeParseJson,
8
7
  valuesToSqliteValues,
9
8
  } from "@evolu/common";
10
9
  import * as Effect from "effect/Effect";
@@ -13,28 +12,28 @@ import * as ExpoSQLite from "expo-sqlite/next";
13
12
 
14
13
  export const SqliteLive = Layer.effect(
15
14
  Sqlite,
16
- Effect.gen(function* (_) {
17
- const config = yield* _(Config);
15
+ Effect.gen(function* () {
16
+ const config = yield* Config;
18
17
  const db = ExpoSQLite.openDatabaseSync(`evolu1-${config.name}.db`);
18
+ const mutex = yield* Effect.makeSemaphore(1);
19
19
 
20
20
  return Sqlite.of({
21
21
  exec: (query) =>
22
- Effect.gen(function* (_) {
22
+ Effect.gen(function* () {
23
23
  const parameters = valuesToSqliteValues(query.parameters || []);
24
-
25
24
  if (!isSqlMutation(query.sql)) {
26
- const rows = (yield* _(
27
- Effect.promise(() => db.getAllAsync(query.sql, parameters)),
28
- maybeLogSqliteQueryExecutionTime(query),
29
- )) as SqliteRow[];
30
- maybeParseJson(rows);
25
+ const rows = (yield* Effect.promise(() =>
26
+ db.getAllAsync(query.sql, parameters),
27
+ ).pipe(maybeLogSqliteQueryExecutionTime(query))) as SqliteRow[];
31
28
  return { rows, changes: 0 };
32
29
  }
33
- const { changes } = yield* _(
34
- Effect.promise(() => db.runAsync(query.sql, parameters)),
30
+ const { changes } = yield* Effect.promise(() =>
31
+ db.runAsync(query.sql, parameters),
35
32
  );
36
33
  return { rows: [], changes };
37
34
  }),
35
+ // RN doesn't need the "last" mode because there are no tabs.
36
+ transaction: () => mutex.withPermits(1),
38
37
  });
39
38
  }),
40
39
  );
package/src/index.ts CHANGED
@@ -1,26 +1,28 @@
1
1
  import {
2
2
  Bip39,
3
- DbWorkerCommonLive,
4
- EvoluCommonLive,
5
- FetchLive,
6
- FlushSyncDefaultLive,
3
+ DbFactory,
4
+ EvoluFactory,
7
5
  InvalidMnemonicError,
8
6
  Mnemonic,
9
- NanoIdGeneratorLive,
10
- SecretBoxLive,
11
- SyncWorkerCommonLive,
12
- makeCreateEvolu,
7
+ SecretBox,
8
+ Sqlite,
9
+ SqliteFactory,
10
+ SyncFactory,
11
+ Time,
12
+ createDb,
13
+ createNanoIdGeneratorLive,
14
+ createSync,
13
15
  } from "@evolu/common";
14
16
  import * as Effect from "effect/Effect";
15
17
  import * as Layer from "effect/Layer";
16
- import {
17
- AppStateLive,
18
- Bip39Live,
19
- DbWorkerLockLive,
20
- SyncLockLive,
21
- } from "./PlatformLive.js";
18
+ // @ts-expect-error https://github.com/ai/nanoid/issues/468
19
+ import { customAlphabet, nanoid } from "nanoid/index.browser.js";
20
+ import { AppStateLive, Bip39Live, SyncLockLive } from "./PlatformLive.js";
22
21
  import { SqliteLive } from "./SqliteLive.js";
23
22
 
23
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
24
+ const NanoIdGeneratorLive = createNanoIdGeneratorLive(customAlphabet, nanoid);
25
+
24
26
  export * from "@evolu/common/public";
25
27
 
26
28
  /** Parse a string to {@link Mnemonic}. */
@@ -31,53 +33,73 @@ export const parseMnemonic: (
31
33
  Effect.runSync,
32
34
  ).parse;
33
35
 
34
- /**
35
- * Create Evolu for React Native.
36
- *
37
- * Tables with a name prefixed with `_` are local-only, which means they are
38
- * never synced. It's useful for device-specific or temporal data.
39
- *
40
- * @example
41
- * import * as S from "@effect/schema/Schema";
42
- * import {
43
- * NonEmptyString1000,
44
- * createEvolu,
45
- * id,
46
- * } from "@evolu/react-native";
47
- *
48
- * const TodoId = id("Todo");
49
- * type TodoId = S.Schema.Type<typeof TodoId>;
50
- *
51
- * const TodoTable = table({
52
- * id: TodoId,
53
- * title: NonEmptyString1000,
54
- * });
55
- * type TodoTable = S.Schema.Type<typeof TodoTable>;
56
- *
57
- * const Database = database({
58
- * // _todo is local-only table
59
- * todo: TodoTable,
60
- * });
61
- * type Database = S.Schema.Type<typeof Database>;
62
- *
63
- * const evolu = createEvolu(Database);
64
- */
65
- export const createEvolu = makeCreateEvolu(
66
- EvoluCommonLive.pipe(
67
- Layer.provide(
68
- Layer.mergeAll(FlushSyncDefaultLive, AppStateLive, DbWorkerCommonLive),
69
- ),
70
- Layer.provide(
71
- Layer.mergeAll(
72
- Bip39Live,
73
- NanoIdGeneratorLive,
74
- SqliteLive,
75
- SyncWorkerCommonLive,
76
- DbWorkerLockLive,
36
+ const SyncFactoryLive = Layer.succeed(SyncFactory, {
37
+ createSync: Effect.provide(createSync, SecretBox.Live),
38
+ });
39
+
40
+ const SqliteFactoryLive = Layer.succeed(SqliteFactory, {
41
+ createSqlite: Sqlite.pipe(Effect.provide(SqliteLive)),
42
+ });
43
+
44
+ export const EvoluFactoryReactNative = Layer.provide(
45
+ EvoluFactory.Common,
46
+ Layer.mergeAll(
47
+ Layer.succeed(DbFactory, {
48
+ createDb: createDb.pipe(
49
+ Effect.provide(
50
+ Layer.mergeAll(
51
+ Bip39Live,
52
+ SqliteFactory.Common.pipe(
53
+ Layer.provide(SqliteFactoryLive),
54
+ Layer.provide(NanoIdGeneratorLive),
55
+ ),
56
+ NanoIdGeneratorLive,
57
+ Time.Live,
58
+ SyncFactoryLive,
59
+ SyncLockLive,
60
+ ),
61
+ ),
77
62
  ),
78
- ),
79
- Layer.provide(Layer.mergeAll(SecretBoxLive, SyncLockLive, FetchLive)),
63
+ }),
64
+ NanoIdGeneratorLive,
65
+ AppStateLive,
80
66
  ),
81
67
  );
82
68
 
69
+ // JSDoc doesn't support destructured parameters, so we must copy-paste
70
+ // createEvolu docs from `evolu-common/src/Evolu.ts`.
71
+ // https://github.com/microsoft/TypeScript/issues/11859
72
+ export const {
73
+ /**
74
+ * Create Evolu from the database schema.
75
+ *
76
+ * Tables with a name prefixed with `_` are local-only, which means they are
77
+ * never synced. It's useful for device-specific or temporal data.
78
+ *
79
+ * @example
80
+ * import * as S from "@effect/schema/Schema";
81
+ * import * as E from "@evolu/react-native";
82
+ *
83
+ * const TodoId = E.id("Todo");
84
+ * type TodoId = S.Schema.Type<typeof TodoId>;
85
+ *
86
+ * const TodoTable = E.table({
87
+ * id: TodoId,
88
+ * title: E.NonEmptyString1000,
89
+ * });
90
+ * type TodoTable = S.Schema.Type<typeof TodoTable>;
91
+ *
92
+ * const Database = E.database({
93
+ * todo: TodoTable,
94
+ *
95
+ * // Prefix `_` makes the table local-only (it will not sync)
96
+ * _todo: TodoTable,
97
+ * });
98
+ * type Database = S.Schema.Type<typeof Database>;
99
+ *
100
+ * const evolu = E.createEvolu(Database);
101
+ */
102
+ createEvolu,
103
+ } = EvoluFactory.pipe(Effect.provide(EvoluFactoryReactNative), Effect.runSync);
104
+
83
105
  export * from "@evolu/common-react";