@evolu/common 5.4.7 → 6.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/README.md +30 -34
- package/dist/src/Array.d.ts +17 -0
- package/dist/src/Array.d.ts.map +1 -0
- package/dist/src/Array.js +12 -0
- package/dist/src/Assert.d.ts +68 -0
- package/dist/src/Assert.d.ts.map +1 -0
- package/dist/src/Assert.js +77 -0
- package/dist/src/BigInt.d.ts +20 -0
- package/dist/src/BigInt.d.ts.map +1 -0
- package/dist/src/BigInt.js +18 -0
- package/dist/src/Buffer.d.ts +92 -0
- package/dist/src/Buffer.d.ts.map +1 -0
- package/dist/src/Buffer.js +62 -0
- package/dist/src/Callbacks.d.ts +20 -0
- package/dist/src/Callbacks.d.ts.map +1 -0
- package/dist/src/Callbacks.js +18 -0
- package/dist/src/Console.d.ts +78 -0
- package/dist/src/Console.d.ts.map +1 -0
- package/dist/src/Console.js +103 -0
- package/dist/src/Crypto.d.ts +72 -39
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +89 -54
- package/dist/src/Eq.d.ts +97 -0
- package/dist/src/Eq.d.ts.map +1 -0
- package/dist/src/Eq.js +167 -0
- package/dist/src/Error.d.ts +14 -10
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +43 -11
- package/dist/src/Evolu/Config.d.ts +69 -0
- package/dist/src/Evolu/Config.d.ts.map +1 -0
- package/dist/src/Evolu/Config.js +9 -0
- package/dist/src/Evolu/Db.d.ts +126 -0
- package/dist/src/Evolu/Db.d.ts.map +1 -0
- package/dist/src/Evolu/Db.js +774 -0
- package/dist/src/Evolu/Diff.d.ts +43 -0
- package/dist/src/Evolu/Diff.d.ts.map +1 -0
- package/dist/src/Evolu/Diff.js +95 -0
- package/dist/src/Evolu/Evolu.d.ts +334 -0
- package/dist/src/Evolu/Evolu.d.ts.map +1 -0
- package/dist/src/Evolu/Evolu.js +434 -0
- package/dist/src/Evolu/Internal.d.ts +26 -0
- package/dist/src/Evolu/Internal.d.ts.map +1 -0
- package/dist/src/Evolu/Internal.js +25 -0
- package/dist/src/Evolu/Kysely.d.ts +6 -0
- package/dist/src/Evolu/Kysely.d.ts.map +1 -0
- package/dist/src/Evolu/Kysely.js +21 -0
- package/dist/src/Evolu/Owner.d.ts +155 -0
- package/dist/src/Evolu/Owner.d.ts.map +1 -0
- package/dist/src/Evolu/Owner.js +126 -0
- package/dist/src/Evolu/Platform.d.ts +23 -0
- package/dist/src/Evolu/Platform.d.ts.map +1 -0
- package/dist/src/Evolu/Platform.js +1 -0
- package/dist/src/Evolu/Protocol.d.ts +401 -0
- package/dist/src/Evolu/Protocol.d.ts.map +1 -0
- package/dist/src/Evolu/Protocol.js +1151 -0
- package/dist/src/Evolu/Public.d.ts +18 -0
- package/dist/src/Evolu/Public.d.ts.map +1 -0
- package/dist/src/Evolu/Public.js +11 -0
- package/dist/src/Evolu/PublicKysely.d.ts +148 -0
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
- package/dist/src/Evolu/PublicKysely.js +185 -0
- package/dist/src/Evolu/Query.d.ts +63 -0
- package/dist/src/Evolu/Query.d.ts.map +1 -0
- package/dist/src/Evolu/Query.js +61 -0
- package/dist/src/Evolu/Relay.d.ts +13 -0
- package/dist/src/Evolu/Relay.d.ts.map +1 -0
- package/dist/src/Evolu/Relay.js +109 -0
- package/dist/src/Evolu/Schema.d.ts +201 -0
- package/dist/src/Evolu/Schema.d.ts.map +1 -0
- package/dist/src/Evolu/Schema.js +150 -0
- package/dist/src/Evolu/Storage.d.ts +49 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -0
- package/dist/src/Evolu/Storage.js +1111 -0
- package/dist/src/Evolu/Sync.d.ts +59 -0
- package/dist/src/Evolu/Sync.d.ts.map +1 -0
- package/dist/src/Evolu/Sync.js +29 -0
- package/dist/src/Evolu/Timestamp.d.ts +106 -0
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
- package/dist/src/Evolu/Timestamp.js +179 -0
- package/dist/src/Function.d.ts +54 -0
- package/dist/src/Function.d.ts.map +1 -0
- package/dist/src/Function.js +38 -0
- package/dist/src/ManyToManyMap.d.ts +26 -0
- package/dist/src/ManyToManyMap.d.ts.map +1 -0
- package/dist/src/ManyToManyMap.js +92 -0
- package/dist/src/NanoId.d.ts +27 -0
- package/dist/src/NanoId.d.ts.map +1 -0
- package/dist/src/NanoId.js +6 -0
- package/dist/src/Number.d.ts +42 -0
- package/dist/src/Number.d.ts.map +1 -0
- package/dist/src/Number.js +55 -0
- package/dist/src/Object.d.ts +35 -0
- package/dist/src/Object.d.ts.map +1 -0
- package/dist/src/Object.js +36 -0
- package/dist/src/Order.d.ts +90 -0
- package/dist/src/Order.d.ts.map +1 -0
- package/dist/src/Order.js +85 -0
- package/dist/src/Promise.d.ts +180 -0
- package/dist/src/Promise.d.ts.map +1 -0
- package/dist/src/Promise.js +176 -0
- package/dist/src/Random.d.ts +52 -0
- package/dist/src/Random.d.ts.map +1 -0
- package/dist/src/Random.js +29 -0
- package/dist/src/Ref.d.ts +40 -0
- package/dist/src/Ref.d.ts.map +1 -0
- package/dist/src/Ref.js +13 -0
- package/dist/src/Result.d.ts +421 -0
- package/dist/src/Result.d.ts.map +1 -0
- package/dist/src/Result.js +357 -0
- package/dist/src/Skiplist.d.ts +23 -0
- package/dist/src/Skiplist.d.ts.map +1 -0
- package/dist/src/Skiplist.js +58 -0
- package/dist/src/Sqlite.d.ts +116 -52
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +183 -67
- package/dist/src/Store.d.ts +45 -8
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +33 -17
- package/dist/src/String.d.ts +2 -0
- package/dist/src/String.d.ts.map +1 -0
- package/dist/src/String.js +14 -0
- package/dist/src/Time.d.ts +20 -0
- package/dist/src/Time.d.ts.map +1 -0
- package/dist/src/Time.js +25 -0
- package/dist/src/Type.d.ts +1937 -0
- package/dist/src/Type.d.ts.map +1 -0
- package/dist/src/Type.js +2002 -0
- package/dist/src/Types.d.ts +188 -0
- package/dist/src/Types.d.ts.map +1 -0
- package/dist/src/Types.js +6 -0
- package/dist/src/WebSocket.d.ts +112 -0
- package/dist/src/WebSocket.d.ts.map +1 -0
- package/dist/src/WebSocket.js +139 -0
- package/dist/src/Worker.d.ts +44 -0
- package/dist/src/Worker.d.ts.map +1 -0
- package/dist/src/Worker.js +66 -0
- package/dist/src/index.d.ts +24 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -11
- package/package.json +29 -38
- package/src/Array.ts +39 -0
- package/src/Assert.ts +116 -0
- package/src/BigInt.ts +29 -0
- package/src/Buffer.ts +175 -0
- package/src/Callbacks.ts +43 -0
- package/src/Console.ts +159 -0
- package/src/Crypto.ts +169 -115
- package/src/Eq.ts +204 -0
- package/src/Error.ts +57 -20
- package/src/Evolu/Config.ts +83 -0
- package/src/Evolu/Db.ts +1275 -0
- package/src/Evolu/Diff.ts +142 -0
- package/src/Evolu/Evolu.ts +947 -0
- package/src/Evolu/Internal.ts +26 -0
- package/src/Evolu/Kysely.ts +38 -0
- package/src/Evolu/Owner.ts +296 -0
- package/src/Evolu/Platform.ts +27 -0
- package/src/Evolu/Protocol.ts +1857 -0
- package/src/Evolu/Public.ts +43 -0
- package/src/Evolu/PublicKysely.ts +240 -0
- package/src/Evolu/Query.ts +167 -0
- package/src/Evolu/Relay.ts +142 -0
- package/src/Evolu/Schema.ts +417 -0
- package/src/Evolu/Storage.ts +1281 -0
- package/src/Evolu/Sync.ts +105 -0
- package/src/Evolu/Timestamp.ts +311 -0
- package/src/Function.ts +58 -0
- package/src/ManyToManyMap.ts +140 -0
- package/src/NanoId.ts +39 -0
- package/src/Number.ts +90 -0
- package/src/Object.ts +64 -0
- package/src/Order.ts +113 -0
- package/src/Promise.ts +295 -0
- package/src/Random.ts +68 -0
- package/src/Ref.ts +63 -0
- package/src/Result.ts +453 -0
- package/src/Skiplist.ts +102 -0
- package/src/Sqlite.ts +366 -153
- package/src/Store.ts +79 -36
- package/src/String.ts +10 -0
- package/src/Time.ts +36 -0
- package/src/Type.ts +3978 -0
- package/src/Types.ts +209 -0
- package/src/WebSocket.ts +273 -0
- package/src/Worker.ts +129 -0
- package/src/index.ts +24 -11
- package/dist/src/Config.d.ts +0 -56
- package/dist/src/Config.d.ts.map +0 -1
- package/dist/src/Config.js +0 -39
- package/dist/src/Crdt.d.ts +0 -89
- package/dist/src/Crdt.d.ts.map +0 -1
- package/dist/src/Crdt.js +0 -181
- package/dist/src/Db.d.ts +0 -107
- package/dist/src/Db.d.ts.map +0 -1
- package/dist/src/Db.js +0 -443
- package/dist/src/Diff.d.ts +0 -27
- package/dist/src/Diff.d.ts.map +0 -1
- package/dist/src/Diff.js +0 -84
- package/dist/src/Evolu.d.ts +0 -426
- package/dist/src/Evolu.d.ts.map +0 -1
- package/dist/src/Evolu.js +0 -333
- package/dist/src/Model.d.ts +0 -141
- package/dist/src/Model.d.ts.map +0 -1
- package/dist/src/Model.js +0 -125
- package/dist/src/Murmurhash.d.ts +0 -2
- package/dist/src/Murmurhash.d.ts.map +0 -1
- package/dist/src/Murmurhash.js +0 -60
- package/dist/src/Owner.d.ts +0 -33
- package/dist/src/Owner.d.ts.map +0 -1
- package/dist/src/Owner.js +0 -26
- package/dist/src/Platform.d.ts +0 -37
- package/dist/src/Platform.d.ts.map +0 -1
- package/dist/src/Platform.js +0 -11
- package/dist/src/Protobuf.d.ts +0 -81
- package/dist/src/Protobuf.d.ts.map +0 -1
- package/dist/src/Protobuf.js +0 -92
- package/dist/src/Public.d.ts +0 -13
- package/dist/src/Public.d.ts.map +0 -1
- package/dist/src/Public.js +0 -6
- package/dist/src/Socket.d.ts +0 -8
- package/dist/src/Socket.d.ts.map +0 -1
- package/dist/src/Socket.js +0 -51
- package/dist/src/Sql.d.ts +0 -12
- package/dist/src/Sql.d.ts.map +0 -1
- package/dist/src/Sql.js +0 -30
- package/dist/src/Sync.d.ts +0 -70
- package/dist/src/Sync.d.ts.map +0 -1
- package/dist/src/Sync.js +0 -127
- package/src/Config.ts +0 -119
- package/src/Crdt.ts +0 -361
- package/src/Db.ts +0 -955
- package/src/Diff.ts +0 -114
- package/src/Evolu.ts +0 -1016
- package/src/Model.ts +0 -233
- package/src/Murmurhash.ts +0 -70
- package/src/Owner.ts +0 -69
- package/src/Platform.ts +0 -47
- package/src/Protobuf.ts +0 -155
- package/src/Public.ts +0 -12
- package/src/Socket.ts +0 -83
- package/src/Sql.ts +0 -41
- package/src/Sync.ts +0 -315
package/src/Db.ts
DELETED
|
@@ -1,955 +0,0 @@
|
|
|
1
|
-
import * as S from "@effect/schema/Schema";
|
|
2
|
-
import * as Arr from "effect/Array";
|
|
3
|
-
import * as Brand from "effect/Brand";
|
|
4
|
-
import * as Console from "effect/Console";
|
|
5
|
-
import * as Context from "effect/Context";
|
|
6
|
-
import * as Deferred from "effect/Deferred";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
-
import { Equivalence } from "effect/Equivalence";
|
|
9
|
-
import { constVoid, pipe } from "effect/Function";
|
|
10
|
-
import * as Option from "effect/Option";
|
|
11
|
-
import * as Predicate from "effect/Predicate";
|
|
12
|
-
import * as Record from "effect/Record";
|
|
13
|
-
import * as Ref from "effect/Ref";
|
|
14
|
-
import * as Schedule from "effect/Schedule";
|
|
15
|
-
import * as String from "effect/String";
|
|
16
|
-
import * as SynchronizedRef from "effect/SynchronizedRef";
|
|
17
|
-
import * as Kysely from "kysely";
|
|
18
|
-
import { Config } from "./Config.js";
|
|
19
|
-
import {
|
|
20
|
-
MerkleTree,
|
|
21
|
-
Millis,
|
|
22
|
-
Time,
|
|
23
|
-
Timestamp,
|
|
24
|
-
TimestampCounterOverflowError,
|
|
25
|
-
TimestampDriftError,
|
|
26
|
-
TimestampString,
|
|
27
|
-
TimestampTimeOutOfRangeError,
|
|
28
|
-
diffMerkleTrees,
|
|
29
|
-
initialMerkleTree,
|
|
30
|
-
insertIntoMerkleTree,
|
|
31
|
-
makeInitialTimestamp,
|
|
32
|
-
makeSyncTimestamp,
|
|
33
|
-
merkleTreeToString,
|
|
34
|
-
receiveTimestamp,
|
|
35
|
-
sendTimestamp,
|
|
36
|
-
timestampToString,
|
|
37
|
-
unsafeTimestampFromString,
|
|
38
|
-
} from "./Crdt.js";
|
|
39
|
-
import { Mnemonic, NanoIdGenerator } from "./Crypto.js";
|
|
40
|
-
import { QueryPatches, makePatches } from "./Diff.js";
|
|
41
|
-
import {
|
|
42
|
-
EvoluError,
|
|
43
|
-
ensureTransferableError,
|
|
44
|
-
makeUnexpectedError,
|
|
45
|
-
} from "./Error.js";
|
|
46
|
-
import { Id, cast } from "./Model.js";
|
|
47
|
-
import { Owner, OwnerId, makeOwner } from "./Owner.js";
|
|
48
|
-
import { SyncLock } from "./Platform.js";
|
|
49
|
-
import * as Sql from "./Sql.js";
|
|
50
|
-
import {
|
|
51
|
-
JsonObjectOrArray,
|
|
52
|
-
Sqlite,
|
|
53
|
-
SqliteExecResult,
|
|
54
|
-
SqliteFactory,
|
|
55
|
-
SqliteQuery,
|
|
56
|
-
SqliteQueryOptions,
|
|
57
|
-
SqliteQueryPlanRow,
|
|
58
|
-
SqliteTransactionMode,
|
|
59
|
-
Value,
|
|
60
|
-
drawSqliteQueryPlan,
|
|
61
|
-
isJsonObjectOrArray,
|
|
62
|
-
} from "./Sqlite.js";
|
|
63
|
-
import {
|
|
64
|
-
Message,
|
|
65
|
-
NewMessage,
|
|
66
|
-
Sync,
|
|
67
|
-
SyncData,
|
|
68
|
-
SyncFactory,
|
|
69
|
-
SyncResult,
|
|
70
|
-
SyncState,
|
|
71
|
-
} from "./Sync.js";
|
|
72
|
-
|
|
73
|
-
export interface Db {
|
|
74
|
-
readonly init: (
|
|
75
|
-
schema: DbSchema,
|
|
76
|
-
initialData: ReadonlyArray<Mutation>,
|
|
77
|
-
onError: Callbacks["onError"],
|
|
78
|
-
onSyncStateChange: Callbacks["onSyncStateChange"],
|
|
79
|
-
onReceive: Callbacks["onReceive"],
|
|
80
|
-
mnemonic: Mnemonic | undefined,
|
|
81
|
-
) => Effect.Effect<
|
|
82
|
-
Owner,
|
|
83
|
-
| NotSupportedPlatformError
|
|
84
|
-
| TimestampTimeOutOfRangeError
|
|
85
|
-
| TimestampDriftError
|
|
86
|
-
| TimestampCounterOverflowError,
|
|
87
|
-
Config
|
|
88
|
-
>;
|
|
89
|
-
|
|
90
|
-
readonly loadQueries: (
|
|
91
|
-
queries: ReadonlyArray<Query>,
|
|
92
|
-
) => Effect.Effect<ReadonlyArray<QueryPatches>>;
|
|
93
|
-
|
|
94
|
-
readonly mutate: (
|
|
95
|
-
mutations: ReadonlyArray<Mutation>,
|
|
96
|
-
queriesToRefresh: ReadonlyArray<Query>,
|
|
97
|
-
) => Effect.Effect<
|
|
98
|
-
ReadonlyArray<QueryPatches>,
|
|
99
|
-
| TimestampTimeOutOfRangeError
|
|
100
|
-
| TimestampDriftError
|
|
101
|
-
| TimestampCounterOverflowError,
|
|
102
|
-
Config
|
|
103
|
-
>;
|
|
104
|
-
|
|
105
|
-
readonly resetOwner: () => Effect.Effect<void>;
|
|
106
|
-
|
|
107
|
-
readonly restoreOwner: (
|
|
108
|
-
schema: DbSchema,
|
|
109
|
-
mnemonic: Mnemonic,
|
|
110
|
-
) => Effect.Effect<void>;
|
|
111
|
-
|
|
112
|
-
readonly ensureSchema: (schema: DbSchema) => Effect.Effect<void>;
|
|
113
|
-
|
|
114
|
-
readonly sync: (
|
|
115
|
-
queriesToRefresh: ReadonlyArray<Query>,
|
|
116
|
-
) => Effect.Effect<ReadonlyArray<QueryPatches>, never, Config>;
|
|
117
|
-
|
|
118
|
-
readonly exportDatabase: () => Effect.Effect<Uint8Array>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface DbSchema {
|
|
122
|
-
readonly tables: ReadonlyArray<Table>;
|
|
123
|
-
readonly indexes: ReadonlyArray<Index>;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export interface Table {
|
|
127
|
-
readonly name: string;
|
|
128
|
-
readonly columns: ReadonlyArray<string>;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export const Index = S.Struct({
|
|
132
|
-
name: S.String,
|
|
133
|
-
sql: S.String,
|
|
134
|
-
});
|
|
135
|
-
export type Index = typeof Index.Type;
|
|
136
|
-
|
|
137
|
-
export interface Mutation {
|
|
138
|
-
readonly table: string;
|
|
139
|
-
readonly id: Id;
|
|
140
|
-
readonly values: Record.ReadonlyRecord<
|
|
141
|
-
string,
|
|
142
|
-
Value | Date | boolean | undefined
|
|
143
|
-
>;
|
|
144
|
-
readonly isInsert: boolean;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface NotSupportedPlatformError {
|
|
148
|
-
readonly _tag: "NotSupportedPlatformError";
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// https://blog.beraliv.dev/2021-05-07-opaque-type-in-typescript
|
|
152
|
-
declare const __queryBrand: unique symbol;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Query is SQL query serialized as a string with a branded type representing a
|
|
156
|
-
* row it returns.
|
|
157
|
-
*/
|
|
158
|
-
export type Query<R extends Row = Row> = string &
|
|
159
|
-
Brand.Brand<"Query"> & { readonly [__queryBrand]: R };
|
|
160
|
-
|
|
161
|
-
export type Row = {
|
|
162
|
-
[key: string]:
|
|
163
|
-
| Value
|
|
164
|
-
| Row // for jsonObjectFrom from kysely/helpers/sqlite
|
|
165
|
-
| ReadonlyArray<Row>; // for jsonArrayFrom from kysely/helpers/sqlite
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export class DbFactory extends Context.Tag("DbFactory")<
|
|
169
|
-
DbFactory,
|
|
170
|
-
{
|
|
171
|
-
readonly createDb: Effect.Effect<Db>;
|
|
172
|
-
}
|
|
173
|
-
>() {}
|
|
174
|
-
|
|
175
|
-
export const createDb: Effect.Effect<
|
|
176
|
-
Db,
|
|
177
|
-
never,
|
|
178
|
-
SqliteFactory | NanoIdGenerator | Time | SyncFactory | SyncLock
|
|
179
|
-
> = Effect.gen(function* () {
|
|
180
|
-
const { createSqlite } = yield* SqliteFactory;
|
|
181
|
-
const { createSync } = yield* SyncFactory;
|
|
182
|
-
|
|
183
|
-
const initContext = Context.empty().pipe(
|
|
184
|
-
Context.add(NanoIdGenerator, yield* NanoIdGenerator),
|
|
185
|
-
Context.add(Time, yield* Time),
|
|
186
|
-
Context.add(SyncLock, yield* SyncLock),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const afterInitContext =
|
|
190
|
-
yield* Deferred.make<
|
|
191
|
-
Context.Context<
|
|
192
|
-
NanoIdGenerator | Time | SyncLock | Sqlite | Owner | Sync | Callbacks
|
|
193
|
-
>
|
|
194
|
-
>();
|
|
195
|
-
|
|
196
|
-
const afterInit =
|
|
197
|
-
(options: { readonly transaction: SqliteTransactionMode }) =>
|
|
198
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
199
|
-
Effect.flatMap(Deferred.await(afterInitContext), (context) =>
|
|
200
|
-
Effect.flatMap(Sqlite, (sqlite) =>
|
|
201
|
-
sqlite.transaction(options.transaction)(effect),
|
|
202
|
-
).pipe(Effect.provide(context)),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
const queryRowsRef = yield* SynchronizedRef.make<QueryRowsMap>(new Map());
|
|
206
|
-
|
|
207
|
-
const db: Db = {
|
|
208
|
-
init: (
|
|
209
|
-
schema,
|
|
210
|
-
initialData,
|
|
211
|
-
onError,
|
|
212
|
-
onSyncStateChange,
|
|
213
|
-
onReceive,
|
|
214
|
-
mnemonic,
|
|
215
|
-
) =>
|
|
216
|
-
Effect.gen(function* () {
|
|
217
|
-
yield* Effect.logDebug(["Db init", { schema }]);
|
|
218
|
-
const sqlite = yield* createSqlite;
|
|
219
|
-
const contextWithSqlite = Context.add(initContext, Sqlite, sqlite);
|
|
220
|
-
const owner = yield* getSchema.pipe(
|
|
221
|
-
Effect.tap(ensureSchema(schema)),
|
|
222
|
-
Effect.flatMap((currentSchema) => {
|
|
223
|
-
if (currentSchema.tables.map((t) => t.name).includes("evolu_owner"))
|
|
224
|
-
return readOwner;
|
|
225
|
-
return createOwner(mnemonic).pipe(
|
|
226
|
-
Effect.tap(applyMutations(initialData)),
|
|
227
|
-
);
|
|
228
|
-
}),
|
|
229
|
-
sqlite.transaction("exclusive"),
|
|
230
|
-
Effect.provide(contextWithSqlite),
|
|
231
|
-
);
|
|
232
|
-
const sync = yield* createSync.pipe(Effect.provide(initContext));
|
|
233
|
-
yield* sync.init(owner);
|
|
234
|
-
Deferred.unsafeDone(
|
|
235
|
-
afterInitContext,
|
|
236
|
-
Effect.succeed(
|
|
237
|
-
contextWithSqlite.pipe(
|
|
238
|
-
Context.add(Owner, owner),
|
|
239
|
-
Context.add(Sync, sync),
|
|
240
|
-
Context.add(Callbacks, { onError, onSyncStateChange, onReceive }),
|
|
241
|
-
),
|
|
242
|
-
),
|
|
243
|
-
);
|
|
244
|
-
return owner;
|
|
245
|
-
}),
|
|
246
|
-
|
|
247
|
-
loadQueries: (queries) =>
|
|
248
|
-
Effect.logDebug(["Db loadQueries", { queries }]).pipe(
|
|
249
|
-
Effect.zipRight(loadQueries(queries, queryRowsRef)),
|
|
250
|
-
afterInit({ transaction: "shared" }),
|
|
251
|
-
),
|
|
252
|
-
|
|
253
|
-
mutate: (mutations, queriesToRefresh) =>
|
|
254
|
-
Effect.gen(function* () {
|
|
255
|
-
yield* Effect.logDebug(["Db mutate", { mutations, queriesToRefresh }]);
|
|
256
|
-
const time = yield* Time;
|
|
257
|
-
const sqlite = yield* Sqlite;
|
|
258
|
-
|
|
259
|
-
const [toSyncMutations, localOnlyMutations] = Arr.partition(
|
|
260
|
-
mutations,
|
|
261
|
-
/** Table name starting with '_' is local only (not synced). */
|
|
262
|
-
(mutation) => mutation.table.startsWith("_"),
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
for (const mutation of localOnlyMutations) {
|
|
266
|
-
const isDeleteMutation = mutationToNewMessages(mutation).some(
|
|
267
|
-
({ column, value }) => column === "isDeleted" && value === 1,
|
|
268
|
-
);
|
|
269
|
-
if (isDeleteMutation) {
|
|
270
|
-
yield* sqlite.exec({
|
|
271
|
-
sql: `delete from "${mutation.table}" where "id" = ?;`,
|
|
272
|
-
parameters: [mutation.id],
|
|
273
|
-
});
|
|
274
|
-
} else {
|
|
275
|
-
const messages = mutationToNewMessages(mutation);
|
|
276
|
-
for (const message of messages) {
|
|
277
|
-
const now = yield* time.now;
|
|
278
|
-
yield* upsertValueIntoTableRowColumn(message, messages, now);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (toSyncMutations.length > 0) {
|
|
283
|
-
yield* Effect.tap(applyMutations(toSyncMutations), forkSync);
|
|
284
|
-
}
|
|
285
|
-
return yield* loadQueries(queriesToRefresh, queryRowsRef);
|
|
286
|
-
}).pipe(afterInit({ transaction: "exclusive" })),
|
|
287
|
-
|
|
288
|
-
resetOwner: () =>
|
|
289
|
-
Effect.logTrace("Db resetOwner").pipe(
|
|
290
|
-
Effect.tap(dropAllTables),
|
|
291
|
-
afterInit({ transaction: "last" }),
|
|
292
|
-
),
|
|
293
|
-
|
|
294
|
-
restoreOwner: (schema, mnemonic) =>
|
|
295
|
-
Effect.logTrace("Db restoreOwner").pipe(
|
|
296
|
-
Effect.tap(dropAllTables),
|
|
297
|
-
Effect.tap(Effect.flatMap(getSchema, ensureSchema(schema))),
|
|
298
|
-
Effect.tap(createOwner(mnemonic)),
|
|
299
|
-
afterInit({ transaction: "last" }),
|
|
300
|
-
),
|
|
301
|
-
|
|
302
|
-
ensureSchema: (schema) =>
|
|
303
|
-
getSchema.pipe(
|
|
304
|
-
Effect.flatMap(ensureSchema(schema)),
|
|
305
|
-
afterInit({ transaction: "exclusive" }),
|
|
306
|
-
),
|
|
307
|
-
|
|
308
|
-
sync: (queriesToRefresh) =>
|
|
309
|
-
Effect.logDebug(["Db sync", { queriesToRefresh }]).pipe(
|
|
310
|
-
Effect.zipRight(forkSync()),
|
|
311
|
-
Effect.zipRight(loadQueries(queriesToRefresh, queryRowsRef)),
|
|
312
|
-
afterInit({ transaction: "shared" }),
|
|
313
|
-
),
|
|
314
|
-
|
|
315
|
-
exportDatabase: () =>
|
|
316
|
-
Effect.logTrace("Db exportDatabase").pipe(
|
|
317
|
-
Effect.zipRight(Sqlite),
|
|
318
|
-
Effect.flatMap((sqlite) => sqlite.export()),
|
|
319
|
-
afterInit({ transaction: "exclusive" }),
|
|
320
|
-
),
|
|
321
|
-
|
|
322
|
-
// TODO:
|
|
323
|
-
// dispose: () =>
|
|
324
|
-
// Effect.logTrace("Db dispose").pipe(
|
|
325
|
-
// Effect.tap(Scope.close(scope, Exit.succeed("Db disposed"))),
|
|
326
|
-
// afterInit({ transaction: "last" }),
|
|
327
|
-
// ),
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
return db;
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
export interface Callbacks {
|
|
334
|
-
readonly onError: (error: EvoluError) => void;
|
|
335
|
-
readonly onSyncStateChange: (state: SyncState) => void;
|
|
336
|
-
readonly onReceive: () => void;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export const Callbacks = Context.GenericTag<Callbacks>("Callbacks");
|
|
340
|
-
|
|
341
|
-
export type QueryRowsMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
|
|
342
|
-
|
|
343
|
-
const loadQueries = (
|
|
344
|
-
queries: ReadonlyArray<Query>,
|
|
345
|
-
queryRowsRef: SynchronizedRef.SynchronizedRef<QueryRowsMap>,
|
|
346
|
-
) =>
|
|
347
|
-
Effect.gen(function* () {
|
|
348
|
-
const sqlite = yield* Sqlite;
|
|
349
|
-
const previousState = yield* SynchronizedRef.getAndUpdateEffect(
|
|
350
|
-
queryRowsRef,
|
|
351
|
-
(previousState) =>
|
|
352
|
-
Effect.map(
|
|
353
|
-
Effect.forEach(queries, (query) => {
|
|
354
|
-
const sqliteQuery = deserializeQuery(query);
|
|
355
|
-
return sqlite.exec(sqliteQuery).pipe(
|
|
356
|
-
Effect.tap(maybeExplainQueryPlan(sqliteQuery)),
|
|
357
|
-
Effect.map(({ rows }) => [query, rows] as const),
|
|
358
|
-
);
|
|
359
|
-
}),
|
|
360
|
-
(queriesRows) => new Map([...previousState, ...queriesRows]),
|
|
361
|
-
),
|
|
362
|
-
);
|
|
363
|
-
const currentState = yield* SynchronizedRef.get(queryRowsRef);
|
|
364
|
-
return queries.map(
|
|
365
|
-
(query): QueryPatches => ({
|
|
366
|
-
query,
|
|
367
|
-
patches: makePatches(
|
|
368
|
-
previousState.get(query),
|
|
369
|
-
currentState.get(query) || [],
|
|
370
|
-
),
|
|
371
|
-
}),
|
|
372
|
-
);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
const maybeExplainQueryPlan = (sqliteQuery: SqliteQuery) => {
|
|
376
|
-
if (!sqliteQuery.options?.logExplainQueryPlan) return Effect.void;
|
|
377
|
-
return Sqlite.pipe(
|
|
378
|
-
Effect.flatMap((sqlite) =>
|
|
379
|
-
sqlite.exec({
|
|
380
|
-
...sqliteQuery,
|
|
381
|
-
sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
|
|
382
|
-
}),
|
|
383
|
-
),
|
|
384
|
-
Effect.tap(Console.log("ExplainQueryPlan", sqliteQuery)),
|
|
385
|
-
Effect.tap(({ rows }) =>
|
|
386
|
-
Console.log(drawSqliteQueryPlan(rows as SqliteQueryPlanRow[])),
|
|
387
|
-
),
|
|
388
|
-
Effect.map(constVoid),
|
|
389
|
-
);
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const getSchema: Effect.Effect<DbSchema, never, Sqlite> = Effect.gen(
|
|
393
|
-
function* (_) {
|
|
394
|
-
yield* Effect.logTrace("Db getSchema");
|
|
395
|
-
const sqlite = yield* Sqlite;
|
|
396
|
-
const tables = yield* Effect.map(
|
|
397
|
-
sqlite.exec({
|
|
398
|
-
// https://til.simonwillison.net/sqlite/list-all-columns-in-a-database
|
|
399
|
-
sql: `
|
|
400
|
-
select
|
|
401
|
-
sqlite_master.name as tableName,
|
|
402
|
-
table_info.name as columnName
|
|
403
|
-
from
|
|
404
|
-
sqlite_master
|
|
405
|
-
join pragma_table_info(sqlite_master.name) as table_info
|
|
406
|
-
`.trim(),
|
|
407
|
-
}),
|
|
408
|
-
({ rows }) => {
|
|
409
|
-
const map = new Map<string, string[]>();
|
|
410
|
-
rows.forEach((row) => {
|
|
411
|
-
const { tableName, columnName } = row as {
|
|
412
|
-
tableName: string;
|
|
413
|
-
columnName: string;
|
|
414
|
-
};
|
|
415
|
-
if (!map.has(tableName)) map.set(tableName, []);
|
|
416
|
-
map.get(tableName)?.push(columnName);
|
|
417
|
-
});
|
|
418
|
-
return globalThis.Array.from(map, ([name, columns]) => ({
|
|
419
|
-
name,
|
|
420
|
-
columns,
|
|
421
|
-
}));
|
|
422
|
-
},
|
|
423
|
-
);
|
|
424
|
-
|
|
425
|
-
const indexes = yield* Effect.map(
|
|
426
|
-
sqlite.exec({
|
|
427
|
-
sql: `
|
|
428
|
-
select
|
|
429
|
-
name, sql
|
|
430
|
-
from
|
|
431
|
-
sqlite_master
|
|
432
|
-
where
|
|
433
|
-
type='index' and
|
|
434
|
-
name not like 'sqlite_%' and
|
|
435
|
-
name not like 'index_evolu_%'
|
|
436
|
-
`.trim(),
|
|
437
|
-
}),
|
|
438
|
-
(result) =>
|
|
439
|
-
Arr.map(
|
|
440
|
-
result.rows,
|
|
441
|
-
(row): Index => ({
|
|
442
|
-
name: row.name as string,
|
|
443
|
-
/**
|
|
444
|
-
* SQLite returns "CREATE INDEX" for "create index" for some reason.
|
|
445
|
-
* Other keywords remain unchanged. We have to normalize the casing
|
|
446
|
-
* for `indexEquivalence` manually.
|
|
447
|
-
*/
|
|
448
|
-
sql: (row.sql as string).replace("CREATE INDEX", "create index"),
|
|
449
|
-
}),
|
|
450
|
-
),
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
return { tables, indexes };
|
|
454
|
-
},
|
|
455
|
-
);
|
|
456
|
-
|
|
457
|
-
const ensureSchema = (newSchema: DbSchema) => (currentSchema: DbSchema) =>
|
|
458
|
-
Effect.gen(function* () {
|
|
459
|
-
yield* Effect.logTrace("Db ensureSchema");
|
|
460
|
-
const sql: string[] = [];
|
|
461
|
-
|
|
462
|
-
newSchema.tables.forEach((table) => {
|
|
463
|
-
const currentTable = currentSchema.tables.find(
|
|
464
|
-
(t) => t.name === table.name,
|
|
465
|
-
);
|
|
466
|
-
if (!currentTable) {
|
|
467
|
-
sql.push(
|
|
468
|
-
`
|
|
469
|
-
create table ${table.name} (
|
|
470
|
-
"id" text primary key,
|
|
471
|
-
${table.columns
|
|
472
|
-
.filter((c) => c !== "id")
|
|
473
|
-
// "A column with affinity BLOB does not prefer one storage class over another
|
|
474
|
-
// and no attempt is made to coerce data from one storage class into another."
|
|
475
|
-
// https://www.sqlite.org/datatype3.html
|
|
476
|
-
.map((name) => `"${name}" blob`)
|
|
477
|
-
.join(", ")}
|
|
478
|
-
);`.trim(),
|
|
479
|
-
);
|
|
480
|
-
} else {
|
|
481
|
-
Arr.differenceWith(String.Equivalence)(
|
|
482
|
-
table.columns,
|
|
483
|
-
currentTable.columns,
|
|
484
|
-
).forEach((newColumn) => {
|
|
485
|
-
sql.push(
|
|
486
|
-
`alter table "${table.name}" add column "${newColumn}" blob;`,
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// Remove old indexes.
|
|
493
|
-
Arr.differenceWith(indexEquivalence)(
|
|
494
|
-
currentSchema.indexes,
|
|
495
|
-
Arr.intersectionWith(indexEquivalence)(
|
|
496
|
-
currentSchema.indexes,
|
|
497
|
-
newSchema.indexes,
|
|
498
|
-
),
|
|
499
|
-
).forEach((indexToDrop) => {
|
|
500
|
-
sql.push(`drop index "${indexToDrop.name}";`);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
// Add new indexes.
|
|
504
|
-
Arr.differenceWith(indexEquivalence)(
|
|
505
|
-
newSchema.indexes,
|
|
506
|
-
currentSchema.indexes,
|
|
507
|
-
).forEach((newIndex) => {
|
|
508
|
-
sql.push(`${newIndex.sql};`);
|
|
509
|
-
});
|
|
510
|
-
if (sql.length > 0) {
|
|
511
|
-
const sqlite = yield* Sqlite;
|
|
512
|
-
yield* sqlite.exec({ sql: sql.join("\n") });
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
const indexEquivalence: Equivalence<Index> = (self, that) =>
|
|
517
|
-
self.name === that.name && self.sql === that.sql;
|
|
518
|
-
|
|
519
|
-
const readOwner = Effect.logTrace("Db readOwner").pipe(
|
|
520
|
-
Effect.zipRight(Sqlite),
|
|
521
|
-
Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwner)),
|
|
522
|
-
Effect.map(
|
|
523
|
-
({ rows: [row] }): Owner => ({
|
|
524
|
-
id: row.id as OwnerId,
|
|
525
|
-
mnemonic: row.mnemonic as Mnemonic,
|
|
526
|
-
encryptionKey: row.encryptionKey as Uint8Array,
|
|
527
|
-
}),
|
|
528
|
-
),
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
const createOwner = (mnemonic: Mnemonic | undefined) =>
|
|
532
|
-
Effect.logTrace("Db createOwner").pipe(
|
|
533
|
-
Effect.zipRight(
|
|
534
|
-
Effect.all([makeOwner(mnemonic), Sqlite, makeInitialTimestamp]),
|
|
535
|
-
),
|
|
536
|
-
Effect.tap(([owner, sqlite, initialTimestampString]) =>
|
|
537
|
-
Effect.all([
|
|
538
|
-
sqlite.exec(Sql.createMessageTable),
|
|
539
|
-
sqlite.exec(Sql.createMessageTableIndex),
|
|
540
|
-
sqlite.exec(Sql.createOwnerTable),
|
|
541
|
-
sqlite.exec({
|
|
542
|
-
...Sql.insertOwner,
|
|
543
|
-
parameters: [
|
|
544
|
-
owner.id,
|
|
545
|
-
owner.mnemonic,
|
|
546
|
-
owner.encryptionKey,
|
|
547
|
-
timestampToString(initialTimestampString),
|
|
548
|
-
merkleTreeToString(initialMerkleTree),
|
|
549
|
-
],
|
|
550
|
-
}),
|
|
551
|
-
]),
|
|
552
|
-
),
|
|
553
|
-
Effect.map(([owner]) => owner),
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
const applyMutations = (mutations: ReadonlyArray<Mutation>) =>
|
|
557
|
-
Effect.gen(function* (_) {
|
|
558
|
-
const { timestamp, merkleTree } = yield* readTimestampAndMerkleTree;
|
|
559
|
-
const [nextTimestamp, messages] = yield* Effect.mapAccum(
|
|
560
|
-
mutations.flatMap(mutationToNewMessages),
|
|
561
|
-
timestamp,
|
|
562
|
-
(currentTimestamp, newMessage) =>
|
|
563
|
-
Effect.map(sendTimestamp(currentTimestamp), (nextTimestamp) => {
|
|
564
|
-
const message: Message = {
|
|
565
|
-
...newMessage,
|
|
566
|
-
timestamp: timestampToString(nextTimestamp),
|
|
567
|
-
};
|
|
568
|
-
return [nextTimestamp, message];
|
|
569
|
-
}),
|
|
570
|
-
);
|
|
571
|
-
const nextMerkleTree = yield* applyMessages(merkleTree, messages);
|
|
572
|
-
yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
|
|
573
|
-
return messages;
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
const readTimestampAndMerkleTree = Sqlite.pipe(
|
|
577
|
-
Effect.flatMap((sqlite) =>
|
|
578
|
-
sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree),
|
|
579
|
-
),
|
|
580
|
-
Effect.map(({ rows: [{ timestamp, merkleTree }] }) => ({
|
|
581
|
-
timestamp: unsafeTimestampFromString(timestamp as TimestampString),
|
|
582
|
-
merkleTree: merkleTree as MerkleTree,
|
|
583
|
-
})),
|
|
584
|
-
);
|
|
585
|
-
|
|
586
|
-
const mutationToNewMessages = (mutation: Mutation) =>
|
|
587
|
-
pipe(
|
|
588
|
-
Object.entries(mutation.values),
|
|
589
|
-
Arr.filterMap(([column, value]) =>
|
|
590
|
-
// The value can be undefined if exactOptionalPropertyTypes isn't true.
|
|
591
|
-
// Don't insert nulls because null is the default value.
|
|
592
|
-
value === undefined || (mutation.isInsert && value == null)
|
|
593
|
-
? Option.none()
|
|
594
|
-
: Option.some([column, value] as const),
|
|
595
|
-
),
|
|
596
|
-
Arr.map(
|
|
597
|
-
([column, value]): NewMessage => ({
|
|
598
|
-
table: mutation.table,
|
|
599
|
-
row: mutation.id,
|
|
600
|
-
column,
|
|
601
|
-
value:
|
|
602
|
-
typeof value === "boolean"
|
|
603
|
-
? cast(value)
|
|
604
|
-
: value instanceof Date
|
|
605
|
-
? cast(value)
|
|
606
|
-
: value,
|
|
607
|
-
}),
|
|
608
|
-
),
|
|
609
|
-
);
|
|
610
|
-
|
|
611
|
-
const applyMessages = (
|
|
612
|
-
merkleTree: MerkleTree,
|
|
613
|
-
messages: ReadonlyArray<Message>,
|
|
614
|
-
): Effect.Effect<MerkleTree, never, Sqlite> =>
|
|
615
|
-
Effect.gen(function* () {
|
|
616
|
-
const sqlite = yield* Sqlite;
|
|
617
|
-
for (const message of messages) {
|
|
618
|
-
const messageTimestamp = unsafeTimestampFromString(message.timestamp);
|
|
619
|
-
const lastTimestamp = yield* Effect.map(
|
|
620
|
-
sqlite.exec({
|
|
621
|
-
...Sql.selectLastTimestampForTableRowColumn,
|
|
622
|
-
parameters: [message.table, message.row, message.column, 1],
|
|
623
|
-
}),
|
|
624
|
-
({ rows }) =>
|
|
625
|
-
rows.length > 0 ? (rows[0].timestamp as TimestampString) : null,
|
|
626
|
-
);
|
|
627
|
-
if (lastTimestamp == null || lastTimestamp < message.timestamp) {
|
|
628
|
-
yield* upsertValueIntoTableRowColumn(
|
|
629
|
-
message,
|
|
630
|
-
messages,
|
|
631
|
-
messageTimestamp.millis,
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
if (lastTimestamp == null || lastTimestamp !== message.timestamp) {
|
|
635
|
-
const { changes } = yield* sqlite.exec({
|
|
636
|
-
...Sql.insertIntoMessagesIfNew,
|
|
637
|
-
parameters: [
|
|
638
|
-
message.timestamp,
|
|
639
|
-
message.table,
|
|
640
|
-
message.row,
|
|
641
|
-
message.column,
|
|
642
|
-
message.value,
|
|
643
|
-
],
|
|
644
|
-
});
|
|
645
|
-
if (changes === 1)
|
|
646
|
-
merkleTree = insertIntoMerkleTree(merkleTree, messageTimestamp);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return merkleTree;
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
export const upsertValueIntoTableRowColumn = (
|
|
653
|
-
message: NewMessage,
|
|
654
|
-
messages: ReadonlyArray<NewMessage>,
|
|
655
|
-
millis: Millis,
|
|
656
|
-
): Effect.Effect<SqliteExecResult, never, Sqlite> =>
|
|
657
|
-
Sqlite.pipe(
|
|
658
|
-
Effect.map((sqlite) => {
|
|
659
|
-
const now = cast(new Date(millis));
|
|
660
|
-
return sqlite.exec({
|
|
661
|
-
sql: `
|
|
662
|
-
insert into
|
|
663
|
-
"${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
|
|
664
|
-
values
|
|
665
|
-
(?, ?, ?, ?)
|
|
666
|
-
on conflict do update set
|
|
667
|
-
"${message.column}" = ?,
|
|
668
|
-
"updatedAt" = ?
|
|
669
|
-
`.trim(),
|
|
670
|
-
parameters: [message.row, message.value, now, now, message.value, now],
|
|
671
|
-
});
|
|
672
|
-
}),
|
|
673
|
-
Effect.flatMap((insert) =>
|
|
674
|
-
Effect.catchSomeDefect(insert, (error) =>
|
|
675
|
-
S.is(SqliteNoSuchTableOrColumnError)(error)
|
|
676
|
-
? Option.some(
|
|
677
|
-
// If one message fails, we ensure schema for all messages.
|
|
678
|
-
ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)),
|
|
679
|
-
)
|
|
680
|
-
: Option.none(),
|
|
681
|
-
),
|
|
682
|
-
),
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
const SqliteNoSuchTableOrColumnError = S.Struct({
|
|
686
|
-
message: S.Union(
|
|
687
|
-
S.String.pipe(S.includes("no such table")),
|
|
688
|
-
S.String.pipe(S.includes("no such column")),
|
|
689
|
-
S.String.pipe(S.includes("has no column")),
|
|
690
|
-
),
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
const ensureSchemaByNewMessages = (messages: ReadonlyArray<NewMessage>) =>
|
|
694
|
-
Effect.gen(function* () {
|
|
695
|
-
const tablesMap = new Map<string, Table>();
|
|
696
|
-
messages.forEach((message) => {
|
|
697
|
-
const table = tablesMap.get(message.table);
|
|
698
|
-
if (table == null) {
|
|
699
|
-
tablesMap.set(message.table, {
|
|
700
|
-
name: message.table,
|
|
701
|
-
columns: [message.column, "createdAt", "updatedAt"],
|
|
702
|
-
});
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
if (table.columns.includes(message.column)) return;
|
|
706
|
-
tablesMap.set(message.table, {
|
|
707
|
-
name: message.table,
|
|
708
|
-
columns: table.columns.concat(message.column),
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
const tables = Arr.fromIterable(tablesMap.values());
|
|
712
|
-
yield* Effect.flatMap(getSchema, ensureSchema({ tables, indexes: [] }));
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
const writeTimestampAndMerkleTree = (
|
|
716
|
-
timestamp: Timestamp,
|
|
717
|
-
merkleTree: MerkleTree,
|
|
718
|
-
) =>
|
|
719
|
-
Effect.flatMap(Sqlite, (sqlite) =>
|
|
720
|
-
sqlite.exec({
|
|
721
|
-
...Sql.updateOwnerTimestampAndMerkleTree,
|
|
722
|
-
parameters: [
|
|
723
|
-
merkleTreeToString(merkleTree),
|
|
724
|
-
timestampToString(timestamp),
|
|
725
|
-
],
|
|
726
|
-
}),
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
const dropAllTables = Effect.gen(function* () {
|
|
730
|
-
yield* Effect.logTrace("Db dropAllTables");
|
|
731
|
-
const sqlite = yield* Sqlite;
|
|
732
|
-
const schema = yield* getSchema;
|
|
733
|
-
yield* Effect.forEach(schema.tables, (table) =>
|
|
734
|
-
// The dropped table is completely removed from the database schema and
|
|
735
|
-
// the disk file. The table can not be recovered.
|
|
736
|
-
// All indices and triggers associated with the table are also deleted.
|
|
737
|
-
// https://sqlite.org/lang_droptable.html
|
|
738
|
-
sqlite.exec({ sql: `drop table "${table.name}"` }),
|
|
739
|
-
);
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
const forkSync = (messages: ReadonlyArray<Message> = []) =>
|
|
743
|
-
SyncLock.pipe(
|
|
744
|
-
Effect.flatMap((syncLock) => syncLock.tryAcquire),
|
|
745
|
-
Effect.matchEffect({
|
|
746
|
-
onFailure: () => Effect.void,
|
|
747
|
-
onSuccess: () => syncLoop(messages),
|
|
748
|
-
}),
|
|
749
|
-
Effect.scoped,
|
|
750
|
-
Effect.forkDaemon,
|
|
751
|
-
);
|
|
752
|
-
|
|
753
|
-
const syncLoop = (
|
|
754
|
-
messages: ReadonlyArray<Message> = [],
|
|
755
|
-
): Effect.Effect<void, never, Sync | Callbacks | Time | Sqlite | Config> =>
|
|
756
|
-
Effect.gen(function* (_) {
|
|
757
|
-
const sqlite = yield* Sqlite;
|
|
758
|
-
const sync = yield* Sync;
|
|
759
|
-
const callbacks = yield* Callbacks;
|
|
760
|
-
const time = yield* Time;
|
|
761
|
-
|
|
762
|
-
const syncDataRef = yield* readTimestampAndMerkleTree.pipe(
|
|
763
|
-
sqlite.transaction("shared"),
|
|
764
|
-
Effect.map((a): SyncData => ({ ...a, messages })),
|
|
765
|
-
Effect.flatMap(Ref.make),
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
callbacks.onSyncStateChange({ _tag: "SyncStateIsSyncing" });
|
|
769
|
-
|
|
770
|
-
yield* Ref.get(syncDataRef).pipe(
|
|
771
|
-
Effect.flatMap(sync.sync),
|
|
772
|
-
Effect.flatMap(handleSyncResult),
|
|
773
|
-
Effect.repeat({
|
|
774
|
-
// TODO: Schedule.jittered(Schedule.exponential("10 millis"))
|
|
775
|
-
schedule: Schedule.recurs(50),
|
|
776
|
-
until: Option.match({
|
|
777
|
-
onNone: () =>
|
|
778
|
-
Effect.gen(function* () {
|
|
779
|
-
callbacks.onSyncStateChange({
|
|
780
|
-
_tag: "SyncStateIsSynced",
|
|
781
|
-
time: yield* time.now,
|
|
782
|
-
});
|
|
783
|
-
return true;
|
|
784
|
-
}),
|
|
785
|
-
onSome: (syncData) =>
|
|
786
|
-
Effect.gen(function* () {
|
|
787
|
-
yield* Ref.set(syncDataRef, syncData);
|
|
788
|
-
return false;
|
|
789
|
-
}),
|
|
790
|
-
}),
|
|
791
|
-
}),
|
|
792
|
-
Effect.catchAllDefect((error) =>
|
|
793
|
-
// Db can run in a Web Worker, so we must ensure transferable error.
|
|
794
|
-
Effect.fail(makeUnexpectedError(ensureTransferableError(error))),
|
|
795
|
-
),
|
|
796
|
-
Effect.catchTag("SyncStateIsNotSynced", (state) =>
|
|
797
|
-
Effect.succeed(callbacks.onSyncStateChange(state)),
|
|
798
|
-
),
|
|
799
|
-
Effect.catchAll((error) => Effect.succeed(callbacks.onError(error))),
|
|
800
|
-
);
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
const handleSyncResult = (result: SyncResult) =>
|
|
804
|
-
Effect.flatMap(Sqlite, (sqlite) =>
|
|
805
|
-
Effect.gen(function* (_) {
|
|
806
|
-
const { onReceive } = yield* Callbacks;
|
|
807
|
-
const current = yield* readTimestampAndMerkleTree;
|
|
808
|
-
const nextTimestamp = yield* Effect.reduce(
|
|
809
|
-
result.messages,
|
|
810
|
-
current.timestamp,
|
|
811
|
-
(local, message) =>
|
|
812
|
-
receiveTimestamp({
|
|
813
|
-
local,
|
|
814
|
-
remote: unsafeTimestampFromString(message.timestamp),
|
|
815
|
-
}),
|
|
816
|
-
);
|
|
817
|
-
const nextMerkleTree = yield* applyMessages(
|
|
818
|
-
current.merkleTree,
|
|
819
|
-
result.messages,
|
|
820
|
-
);
|
|
821
|
-
if (result.messages.length > 0) {
|
|
822
|
-
yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
|
|
823
|
-
onReceive();
|
|
824
|
-
}
|
|
825
|
-
return yield* diffMerkleTrees(result.merkleTree, nextMerkleTree).pipe(
|
|
826
|
-
Effect.flatMap((diff) =>
|
|
827
|
-
sqlite.exec({
|
|
828
|
-
...Sql.selectMessagesToSync,
|
|
829
|
-
parameters: [timestampToString(makeSyncTimestamp(diff))],
|
|
830
|
-
}),
|
|
831
|
-
),
|
|
832
|
-
Effect.map(
|
|
833
|
-
({ rows }): SyncData => ({
|
|
834
|
-
messages: rows as unknown as ReadonlyArray<Message>,
|
|
835
|
-
merkleTree: nextMerkleTree,
|
|
836
|
-
timestamp: nextTimestamp,
|
|
837
|
-
}),
|
|
838
|
-
),
|
|
839
|
-
Effect.option,
|
|
840
|
-
);
|
|
841
|
-
}).pipe(sqlite.transaction("exclusive")),
|
|
842
|
-
);
|
|
843
|
-
|
|
844
|
-
interface SerializedSqliteQuery {
|
|
845
|
-
readonly sql: string;
|
|
846
|
-
readonly parameters: (
|
|
847
|
-
| null
|
|
848
|
-
| string
|
|
849
|
-
| number
|
|
850
|
-
| Array<number>
|
|
851
|
-
| { json: JsonObjectOrArray }
|
|
852
|
-
)[];
|
|
853
|
-
readonly options?: SqliteQueryOptions;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// We use queries as keys, hence JSON.stringify.
|
|
857
|
-
export const serializeQuery = <R extends Row>({
|
|
858
|
-
sql,
|
|
859
|
-
parameters = [],
|
|
860
|
-
options,
|
|
861
|
-
}: SqliteQuery): Query<R> => {
|
|
862
|
-
const query: SerializedSqliteQuery = {
|
|
863
|
-
sql,
|
|
864
|
-
parameters: parameters.map((p) =>
|
|
865
|
-
Predicate.isUint8Array(p)
|
|
866
|
-
? Arr.fromIterable(p)
|
|
867
|
-
: isJsonObjectOrArray(p)
|
|
868
|
-
? { json: p }
|
|
869
|
-
: p,
|
|
870
|
-
),
|
|
871
|
-
...(options && { options }),
|
|
872
|
-
};
|
|
873
|
-
return JSON.stringify(query) as Query<R>;
|
|
874
|
-
};
|
|
875
|
-
|
|
876
|
-
export const deserializeQuery = <R extends Row>(
|
|
877
|
-
query: Query<R>,
|
|
878
|
-
): SqliteQuery => {
|
|
879
|
-
const serializedSqliteQuery = JSON.parse(query) as SerializedSqliteQuery;
|
|
880
|
-
return {
|
|
881
|
-
...serializedSqliteQuery,
|
|
882
|
-
parameters: serializedSqliteQuery.parameters.map((p) =>
|
|
883
|
-
Arr.isArray(p)
|
|
884
|
-
? new Uint8Array(p)
|
|
885
|
-
: typeof p === "object" && p != null
|
|
886
|
-
? p.json
|
|
887
|
-
: p,
|
|
888
|
-
),
|
|
889
|
-
};
|
|
890
|
-
};
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Extract {@link Row} from {@link Query} instance.
|
|
894
|
-
*
|
|
895
|
-
* @example
|
|
896
|
-
* const allTodos = evolu.createQuery((db) =>
|
|
897
|
-
* db.selectFrom("todo").selectAll(),
|
|
898
|
-
* );
|
|
899
|
-
* type AllTodosRow = ExtractRow<typeof allTodos>;
|
|
900
|
-
*/
|
|
901
|
-
export type ExtractRow<T extends Query> = T extends Query<infer R> ? R : never;
|
|
902
|
-
|
|
903
|
-
// To preserve identity.
|
|
904
|
-
const _emptyRows: ReadonlyArray<Row> = [];
|
|
905
|
-
export const emptyRows = <R extends Row>(): ReadonlyArray<R> =>
|
|
906
|
-
_emptyRows as ReadonlyArray<R>;
|
|
907
|
-
|
|
908
|
-
/** An object with rows and row properties. */
|
|
909
|
-
export interface QueryResult<R extends Row = Row> {
|
|
910
|
-
/** An array containing all the rows returned by the query. */
|
|
911
|
-
readonly rows: ReadonlyArray<Readonly<Kysely.Simplify<R>>>;
|
|
912
|
-
|
|
913
|
-
/**
|
|
914
|
-
* The first row returned by the query, or null if no rows were returned. This
|
|
915
|
-
* property is useful for queries that are expected to return a single row.
|
|
916
|
-
*/
|
|
917
|
-
readonly row: Readonly<Kysely.Simplify<R>> | null;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
export type Queries<R extends Row = Row> = ReadonlyArray<Query<R>>;
|
|
921
|
-
|
|
922
|
-
export type QueryResultsFromQueries<Q extends Queries> = {
|
|
923
|
-
[P in keyof Q]: Q[P] extends Query<infer R> ? QueryResult<R> : never;
|
|
924
|
-
};
|
|
925
|
-
|
|
926
|
-
export type QueryResultsPromisesFromQueries<Q extends Queries> = {
|
|
927
|
-
[P in keyof Q]: Q[P] extends Query<infer R> ? Promise<QueryResult<R>> : never;
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
// To preserve identity.
|
|
931
|
-
const queryResultCache = new WeakMap<ReadonlyArray<Row>, QueryResult<Row>>();
|
|
932
|
-
export const queryResultFromRows = <R extends Row>(
|
|
933
|
-
rows: ReadonlyArray<R>,
|
|
934
|
-
): QueryResult<R> => {
|
|
935
|
-
let queryResult = queryResultCache.get(rows);
|
|
936
|
-
if (queryResult == null) {
|
|
937
|
-
queryResult = { rows, row: rows[0] };
|
|
938
|
-
queryResultCache.set(rows, queryResult);
|
|
939
|
-
}
|
|
940
|
-
return queryResult as QueryResult<R>;
|
|
941
|
-
};
|
|
942
|
-
|
|
943
|
-
export const notSupportedPlatformWorker: Db = {
|
|
944
|
-
init: () =>
|
|
945
|
-
Effect.fail<NotSupportedPlatformError>({
|
|
946
|
-
_tag: "NotSupportedPlatformError",
|
|
947
|
-
}),
|
|
948
|
-
loadQueries: () => Effect.succeed([]),
|
|
949
|
-
mutate: () => Effect.succeed([]),
|
|
950
|
-
resetOwner: () => Effect.void,
|
|
951
|
-
restoreOwner: () => Effect.void,
|
|
952
|
-
ensureSchema: () => Effect.void,
|
|
953
|
-
sync: () => Effect.succeed([]),
|
|
954
|
-
exportDatabase: () => Effect.succeed(new Uint8Array()),
|
|
955
|
-
};
|