@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.
- package/dist/PlatformLive.d.ts +2 -3
- package/dist/PlatformLive.d.ts.map +1 -1
- package/dist/PlatformLive.js +36 -32
- package/dist/SqliteLive.d.ts.map +1 -1
- package/dist/SqliteLive.js +9 -7
- package/dist/index.d.ts +15 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -15
- package/package.json +9 -9
- package/src/PlatformLive.ts +60 -52
- package/src/SqliteLive.ts +11 -12
- package/src/index.ts +81 -59
package/dist/PlatformLive.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { AppState, Bip39,
|
|
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,
|
|
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"}
|
package/dist/PlatformLive.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppState, Bip39,
|
|
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/) &&
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
}));
|
package/dist/SqliteLive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SqliteLive.d.ts","sourceRoot":"","sources":["../src/SqliteLive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,MAAM,
|
|
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"}
|
package/dist/SqliteLive.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { Config, Sqlite, isSqlMutation, maybeLogSqliteQueryExecutionTime,
|
|
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*
|
|
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*
|
|
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*
|
|
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
|
|
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
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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": "
|
|
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.
|
|
34
|
+
"expo-updates": "^0.24.12"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"eslint": "^8.57.0",
|
|
38
|
-
"expo": "^50.0.
|
|
38
|
+
"expo": "^50.0.17",
|
|
39
39
|
"expo-sqlite": "~13.4.0",
|
|
40
40
|
"react-native": "0.73.6",
|
|
41
|
-
"typescript": "^5.4.
|
|
42
|
-
"vitest": "^1.
|
|
43
|
-
"@evolu/common": "
|
|
44
|
-
"@evolu/common-react": "
|
|
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": "^
|
|
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"
|
package/src/PlatformLive.ts
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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*
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
FetchLive,
|
|
6
|
-
FlushSyncDefaultLive,
|
|
3
|
+
DbFactory,
|
|
4
|
+
EvoluFactory,
|
|
7
5
|
InvalidMnemonicError,
|
|
8
6
|
Mnemonic,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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";
|