@evolu/common 4.1.1 → 5.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 +1 -1
- package/dist/src/Config.d.ts +27 -30
- package/dist/src/Config.d.ts.map +1 -1
- package/dist/src/Config.js +32 -5
- package/dist/src/Crdt.d.ts +5 -4
- package/dist/src/Crdt.d.ts.map +1 -1
- package/dist/src/Crdt.js +27 -24
- package/dist/src/Crypto.d.ts +16 -10
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +24 -11
- package/dist/src/Db.d.ts +58 -78
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +368 -170
- package/dist/src/Diff.d.ts +9 -3
- package/dist/src/Diff.d.ts.map +1 -1
- package/dist/src/Diff.js +12 -11
- package/dist/src/Error.d.ts +15 -0
- package/dist/src/Error.d.ts.map +1 -0
- package/dist/src/Error.js +14 -0
- package/dist/src/Evolu.d.ts +139 -91
- package/dist/src/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu.js +267 -239
- package/dist/src/Model.d.ts +52 -0
- package/dist/src/Model.d.ts.map +1 -1
- package/dist/src/Model.js +47 -5
- package/dist/src/Owner.d.ts.map +1 -1
- package/dist/src/Owner.js +14 -8
- package/dist/src/Platform.d.ts +24 -34
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +9 -31
- package/dist/src/Public.d.ts +3 -5
- package/dist/src/Public.d.ts.map +1 -1
- package/dist/src/Public.js +2 -3
- package/dist/src/Sqlite.d.ts +25 -16
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +28 -20
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Sync.d.ts +70 -0
- package/dist/src/Sync.d.ts.map +1 -0
- package/dist/src/Sync.js +126 -0
- package/dist/src/index.d.ts +2 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -5
- package/package.json +12 -10
- package/src/Config.ts +77 -36
- package/src/Crdt.ts +61 -76
- package/src/Crypto.ts +83 -60
- package/src/Db.ts +782 -352
- package/src/Diff.ts +23 -21
- package/src/Error.ts +29 -0
- package/src/Evolu.ts +599 -499
- package/src/Model.ts +102 -6
- package/src/Owner.ts +24 -24
- package/src/Platform.ts +33 -81
- package/src/Public.ts +3 -5
- package/src/Sqlite.ts +81 -44
- package/src/Sync.ts +306 -0
- package/src/index.ts +2 -5
- package/dist/src/DbWorker.d.ts +0 -82
- package/dist/src/DbWorker.d.ts.map +0 -1
- package/dist/src/DbWorker.js +0 -335
- package/dist/src/ErrorStore.d.ts +0 -29
- package/dist/src/ErrorStore.d.ts.map +0 -1
- package/dist/src/ErrorStore.js +0 -13
- package/dist/src/OnCompletes.d.ts +0 -14
- package/dist/src/OnCompletes.d.ts.map +0 -1
- package/dist/src/OnCompletes.js +0 -25
- package/dist/src/SyncWorker.d.ts +0 -80
- package/dist/src/SyncWorker.d.ts.map +0 -1
- package/dist/src/SyncWorker.js +0 -150
- package/dist/src/Types.d.ts +0 -13
- package/dist/src/Types.d.ts.map +0 -1
- package/dist/src/Types.js +0 -1
- package/src/DbWorker.ts +0 -721
- package/src/ErrorStore.ts +0 -46
- package/src/OnCompletes.ts +0 -51
- package/src/SyncWorker.ts +0 -361
- package/src/Types.ts +0 -11
package/src/DbWorker.ts
DELETED
|
@@ -1,721 +0,0 @@
|
|
|
1
|
-
import * as Context from "effect/Context";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as Function from "effect/Function";
|
|
4
|
-
import { pipe } from "effect/Function";
|
|
5
|
-
import * as Layer from "effect/Layer";
|
|
6
|
-
import * as Match from "effect/Match";
|
|
7
|
-
import * as Option from "effect/Option";
|
|
8
|
-
import * as ReadonlyArray from "effect/ReadonlyArray";
|
|
9
|
-
import * as ReadonlyRecord from "effect/ReadonlyRecord";
|
|
10
|
-
import { Config, ConfigLive } from "./Config.js";
|
|
11
|
-
import {
|
|
12
|
-
MerkleTree,
|
|
13
|
-
Millis,
|
|
14
|
-
Time,
|
|
15
|
-
TimeLive,
|
|
16
|
-
Timestamp,
|
|
17
|
-
TimestampCounterOverflowError,
|
|
18
|
-
TimestampDriftError,
|
|
19
|
-
TimestampError,
|
|
20
|
-
TimestampString,
|
|
21
|
-
TimestampTimeOutOfRangeError,
|
|
22
|
-
diffMerkleTrees,
|
|
23
|
-
insertIntoMerkleTree,
|
|
24
|
-
makeSyncTimestamp,
|
|
25
|
-
merkleTreeToString,
|
|
26
|
-
receiveTimestamp,
|
|
27
|
-
sendTimestamp,
|
|
28
|
-
timestampToString,
|
|
29
|
-
unsafeTimestampFromString,
|
|
30
|
-
} from "./Crdt.js";
|
|
31
|
-
import { Bip39, Mnemonic, NanoIdGenerator } from "./Crypto.js";
|
|
32
|
-
import {
|
|
33
|
-
Queries,
|
|
34
|
-
Query,
|
|
35
|
-
RowsStore,
|
|
36
|
-
RowsStoreLive,
|
|
37
|
-
deserializeQuery,
|
|
38
|
-
dropAllTables,
|
|
39
|
-
ensureSchema,
|
|
40
|
-
lazyInit,
|
|
41
|
-
someDefectToNoSuchTableOrColumnError,
|
|
42
|
-
transaction,
|
|
43
|
-
} from "./Db.js";
|
|
44
|
-
import { QueryPatches, makePatches } from "./Diff.js";
|
|
45
|
-
import { EvoluError, makeUnexpectedError } from "./ErrorStore.js";
|
|
46
|
-
import { Id, cast } from "./Model.js";
|
|
47
|
-
import { OnCompleteId } from "./OnCompletes.js";
|
|
48
|
-
import { Owner, OwnerId } from "./Owner.js";
|
|
49
|
-
import { DbWorkerLock } from "./Platform.js";
|
|
50
|
-
import * as Sql from "./Sql.js";
|
|
51
|
-
import {
|
|
52
|
-
Sqlite,
|
|
53
|
-
SqliteQueryPlanRow,
|
|
54
|
-
SqliteSchema,
|
|
55
|
-
Table,
|
|
56
|
-
Value,
|
|
57
|
-
drawSqliteQueryPlan as drawExplainQueryPlan,
|
|
58
|
-
} from "./Sqlite.js";
|
|
59
|
-
import {
|
|
60
|
-
Message,
|
|
61
|
-
NewMessage,
|
|
62
|
-
NewMessageEquivalence,
|
|
63
|
-
SyncState,
|
|
64
|
-
SyncWorker,
|
|
65
|
-
SyncWorkerOutputSyncResponse,
|
|
66
|
-
SyncWorkerPostMessage,
|
|
67
|
-
} from "./SyncWorker.js";
|
|
68
|
-
import { Messaging } from "./Types.js";
|
|
69
|
-
|
|
70
|
-
export interface DbWorker extends Messaging<DbWorkerInput, DbWorkerOutput> {}
|
|
71
|
-
export const DbWorker = Context.GenericTag<DbWorker>("@services/DbWorker");
|
|
72
|
-
|
|
73
|
-
export type DbWorkerInput =
|
|
74
|
-
| DbWorkerInputInit
|
|
75
|
-
| DbWorkerInputQuery
|
|
76
|
-
| DbWorkerInputMutate
|
|
77
|
-
| DbWorkerInputSync
|
|
78
|
-
| DbWorkerInputReset
|
|
79
|
-
| DbWorkerInputEnsureSchema
|
|
80
|
-
| SyncWorkerOutputSyncResponse;
|
|
81
|
-
|
|
82
|
-
interface DbWorkerInputInit {
|
|
83
|
-
readonly _tag: "init";
|
|
84
|
-
readonly config: Config;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface DbWorkerInputQuery {
|
|
88
|
-
readonly _tag: "query";
|
|
89
|
-
readonly queries: ReadonlyArray.NonEmptyReadonlyArray<Query>;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface DbWorkerInputMutate {
|
|
93
|
-
readonly _tag: "mutate";
|
|
94
|
-
readonly mutations: ReadonlyArray.NonEmptyReadonlyArray<Mutation>;
|
|
95
|
-
readonly queries: Queries;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface DbWorkerInputSync {
|
|
99
|
-
readonly _tag: "sync";
|
|
100
|
-
readonly queries: Queries;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
interface DbWorkerInputReset {
|
|
104
|
-
readonly _tag: "reset";
|
|
105
|
-
readonly mnemonic?: Mnemonic;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
interface DbWorkerInputEnsureSchema extends SqliteSchema {
|
|
109
|
-
readonly _tag: "ensureSchema";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
type DbWorkerOnMessage = DbWorker["onMessage"];
|
|
113
|
-
|
|
114
|
-
const DbWorkerOnMessage = Context.GenericTag<DbWorkerOnMessage>(
|
|
115
|
-
"@services/DbWorkerOnMessage",
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
export type DbWorkerOutput =
|
|
119
|
-
| DbWorkerOutputOnError
|
|
120
|
-
| DbWorkerOutputOnOwner
|
|
121
|
-
| DbWorkerOutputOnQuery
|
|
122
|
-
| DbWorkerOutputOnReceive
|
|
123
|
-
| DbWorkerOutputOnResetOrRestore
|
|
124
|
-
| DbWorkerOutputOnSyncState;
|
|
125
|
-
|
|
126
|
-
export interface DbWorkerOutputOnError {
|
|
127
|
-
readonly _tag: "onError";
|
|
128
|
-
readonly error: EvoluError;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface DbWorkerOutputOnOwner {
|
|
132
|
-
readonly _tag: "onOwner";
|
|
133
|
-
readonly owner: Owner;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export interface DbWorkerOutputOnQuery {
|
|
137
|
-
readonly _tag: "onQuery";
|
|
138
|
-
readonly queriesPatches: ReadonlyArray<QueryPatches>;
|
|
139
|
-
readonly onCompleteIds: ReadonlyArray<OnCompleteId>;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
interface DbWorkerOutputOnReceive {
|
|
143
|
-
readonly _tag: "onReceive";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
interface DbWorkerOutputOnResetOrRestore {
|
|
147
|
-
readonly _tag: "onResetOrRestore";
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
interface DbWorkerOutputOnSyncState {
|
|
151
|
-
readonly _tag: "onSyncState";
|
|
152
|
-
readonly state: SyncState;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export interface Mutation {
|
|
156
|
-
readonly table: string;
|
|
157
|
-
readonly id: Id;
|
|
158
|
-
readonly values: ReadonlyRecord.ReadonlyRecord<
|
|
159
|
-
string,
|
|
160
|
-
Value | Date | boolean | undefined
|
|
161
|
-
>;
|
|
162
|
-
readonly isInsert: boolean;
|
|
163
|
-
readonly onCompleteId: OnCompleteId | null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const init = Effect.gen(function* (_) {
|
|
167
|
-
const sqlite = yield* _(Sqlite);
|
|
168
|
-
|
|
169
|
-
return yield* _(
|
|
170
|
-
sqlite.exec(Sql.selectOwner),
|
|
171
|
-
Effect.map(
|
|
172
|
-
({ rows: [row] }): Owner => ({
|
|
173
|
-
id: row.id as OwnerId,
|
|
174
|
-
mnemonic: row.mnemonic as Mnemonic,
|
|
175
|
-
encryptionKey: row.encryptionKey as Uint8Array,
|
|
176
|
-
}),
|
|
177
|
-
),
|
|
178
|
-
someDefectToNoSuchTableOrColumnError,
|
|
179
|
-
Effect.catchTag("NoSuchTableOrColumnError", () => lazyInit()),
|
|
180
|
-
);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const query = ({
|
|
184
|
-
queries,
|
|
185
|
-
onCompleteIds = [],
|
|
186
|
-
}: {
|
|
187
|
-
readonly queries: Queries;
|
|
188
|
-
readonly onCompleteIds?: ReadonlyArray<OnCompleteId>;
|
|
189
|
-
}): Effect.Effect<void, never, Sqlite | RowsStore | DbWorkerOnMessage> =>
|
|
190
|
-
Effect.gen(function* (_) {
|
|
191
|
-
const sqlite = yield* _(Sqlite);
|
|
192
|
-
const rowsStore = yield* _(RowsStore);
|
|
193
|
-
const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
|
|
194
|
-
|
|
195
|
-
const queriesRows = yield* _(
|
|
196
|
-
ReadonlyArray.dedupe(queries),
|
|
197
|
-
Effect.forEach((query) => {
|
|
198
|
-
const sqliteQuery = deserializeQuery(query);
|
|
199
|
-
return sqlite.exec(sqliteQuery).pipe(
|
|
200
|
-
Effect.map((result) => [query, result.rows] as const),
|
|
201
|
-
Effect.tap(() => {
|
|
202
|
-
if (!sqliteQuery.options?.logExplainQueryPlan) return;
|
|
203
|
-
return sqlite
|
|
204
|
-
.exec({
|
|
205
|
-
...sqliteQuery,
|
|
206
|
-
sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
|
|
207
|
-
})
|
|
208
|
-
.pipe(
|
|
209
|
-
Effect.tap(() => Effect.log("ExplainQueryPlan")),
|
|
210
|
-
Effect.tap(({ rows }) => {
|
|
211
|
-
// Not using Effect.log because of formating
|
|
212
|
-
// eslint-disable-next-line no-console
|
|
213
|
-
console.log(
|
|
214
|
-
drawExplainQueryPlan(rows as SqliteQueryPlanRow[]),
|
|
215
|
-
);
|
|
216
|
-
}),
|
|
217
|
-
);
|
|
218
|
-
}),
|
|
219
|
-
);
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
const previous = rowsStore.getState();
|
|
224
|
-
yield* _(rowsStore.setState(new Map([...previous, ...queriesRows])));
|
|
225
|
-
|
|
226
|
-
const queriesPatches = queriesRows.map(
|
|
227
|
-
([query, rows]): QueryPatches => ({
|
|
228
|
-
query,
|
|
229
|
-
patches: makePatches(previous.get(query), rows),
|
|
230
|
-
}),
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
dbWorkerOnMessage({ _tag: "onQuery", queriesPatches, onCompleteIds });
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
interface TimestampAndMerkleTree {
|
|
237
|
-
readonly timestamp: Timestamp;
|
|
238
|
-
readonly merkleTree: MerkleTree;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const readTimestampAndMerkleTree = Sqlite.pipe(
|
|
242
|
-
Effect.flatMap((sqlite) =>
|
|
243
|
-
sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree),
|
|
244
|
-
),
|
|
245
|
-
Effect.map(
|
|
246
|
-
({ rows: [{ timestamp, merkleTree }] }): TimestampAndMerkleTree => ({
|
|
247
|
-
timestamp: unsafeTimestampFromString(timestamp as TimestampString),
|
|
248
|
-
merkleTree: merkleTree as MerkleTree,
|
|
249
|
-
}),
|
|
250
|
-
),
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
export const mutationsToNewMessages = (
|
|
254
|
-
mutations: ReadonlyArray<Mutation>,
|
|
255
|
-
): ReadonlyArray<NewMessage> =>
|
|
256
|
-
pipe(
|
|
257
|
-
mutations,
|
|
258
|
-
ReadonlyArray.map(({ id, isInsert, table, values }) =>
|
|
259
|
-
pipe(
|
|
260
|
-
Object.entries(values),
|
|
261
|
-
// Filter values.
|
|
262
|
-
ReadonlyArray.filterMap(([key, value]) =>
|
|
263
|
-
// The value can be undefined if exactOptionalPropertyTypes isn't true.
|
|
264
|
-
// Don't insert nulls because null is the default value.
|
|
265
|
-
value === undefined || (isInsert && value == null)
|
|
266
|
-
? Option.none()
|
|
267
|
-
: Option.some([key, value] as const),
|
|
268
|
-
),
|
|
269
|
-
// Cast values.
|
|
270
|
-
ReadonlyArray.map(
|
|
271
|
-
([key, value]) =>
|
|
272
|
-
[
|
|
273
|
-
key,
|
|
274
|
-
typeof value === "boolean"
|
|
275
|
-
? cast(value)
|
|
276
|
-
: value instanceof Date
|
|
277
|
-
? cast(value)
|
|
278
|
-
: value,
|
|
279
|
-
] as const,
|
|
280
|
-
),
|
|
281
|
-
ReadonlyArray.map(
|
|
282
|
-
([key, value]): NewMessage => ({
|
|
283
|
-
table,
|
|
284
|
-
row: id,
|
|
285
|
-
column: key,
|
|
286
|
-
value,
|
|
287
|
-
}),
|
|
288
|
-
),
|
|
289
|
-
),
|
|
290
|
-
),
|
|
291
|
-
(a) => a.flat(),
|
|
292
|
-
ReadonlyArray.dedupeWith(NewMessageEquivalence),
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
const ensureSchemaByNewMessages = (
|
|
296
|
-
messages: ReadonlyArray<NewMessage>,
|
|
297
|
-
): Effect.Effect<void, never, Sqlite> =>
|
|
298
|
-
Effect.gen(function* (_) {
|
|
299
|
-
const tablesMap = new Map<string, Table>();
|
|
300
|
-
messages.forEach((message) => {
|
|
301
|
-
const table = tablesMap.get(message.table);
|
|
302
|
-
if (table == null) {
|
|
303
|
-
tablesMap.set(message.table, {
|
|
304
|
-
name: message.table,
|
|
305
|
-
columns: [message.column, "createdAt", "updatedAt"],
|
|
306
|
-
});
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (table.columns.includes(message.column)) return;
|
|
310
|
-
tablesMap.set(message.table, {
|
|
311
|
-
name: message.table,
|
|
312
|
-
columns: table.columns.concat(message.column),
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
const tables = Array.from(tablesMap.values());
|
|
316
|
-
yield* _(ensureSchema({ tables, indexes: [] }));
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
export const upsertValueIntoTableRowColumn = (
|
|
320
|
-
message: NewMessage,
|
|
321
|
-
messages: ReadonlyArray<NewMessage>,
|
|
322
|
-
millis: Millis,
|
|
323
|
-
): Effect.Effect<void, never, Sqlite> =>
|
|
324
|
-
Effect.gen(function* (_) {
|
|
325
|
-
const sqlite = yield* _(Sqlite);
|
|
326
|
-
const createdAtOrUpdatedAt = cast(new Date(millis));
|
|
327
|
-
const insert = sqlite.exec({
|
|
328
|
-
sql: `
|
|
329
|
-
insert into
|
|
330
|
-
"${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
|
|
331
|
-
values
|
|
332
|
-
(?, ?, ?, ?)
|
|
333
|
-
on conflict do update set
|
|
334
|
-
"${message.column}" = ?,
|
|
335
|
-
"updatedAt" = ?
|
|
336
|
-
`.trim(),
|
|
337
|
-
parameters: [
|
|
338
|
-
message.row,
|
|
339
|
-
message.value,
|
|
340
|
-
createdAtOrUpdatedAt,
|
|
341
|
-
createdAtOrUpdatedAt,
|
|
342
|
-
message.value,
|
|
343
|
-
createdAtOrUpdatedAt,
|
|
344
|
-
],
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
yield* _(
|
|
348
|
-
insert,
|
|
349
|
-
someDefectToNoSuchTableOrColumnError,
|
|
350
|
-
Effect.catchTag("NoSuchTableOrColumnError", () =>
|
|
351
|
-
// If one message fails, we ensure schema for all messages.
|
|
352
|
-
ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)),
|
|
353
|
-
),
|
|
354
|
-
);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const applyMessages = ({
|
|
358
|
-
merkleTree,
|
|
359
|
-
messages,
|
|
360
|
-
}: {
|
|
361
|
-
merkleTree: MerkleTree;
|
|
362
|
-
messages: ReadonlyArray<Message>;
|
|
363
|
-
}): Effect.Effect<MerkleTree, never, Sqlite> =>
|
|
364
|
-
Effect.gen(function* (_) {
|
|
365
|
-
const sqlite = yield* _(Sqlite);
|
|
366
|
-
|
|
367
|
-
for (const message of messages) {
|
|
368
|
-
const timestamp: TimestampString | null = yield* _(
|
|
369
|
-
sqlite.exec({
|
|
370
|
-
...Sql.selectLastTimestampForTableRowColumn,
|
|
371
|
-
parameters: [message.table, message.row, message.column, 1],
|
|
372
|
-
}),
|
|
373
|
-
Effect.map((result) => result.rows),
|
|
374
|
-
Effect.flatMap(ReadonlyArray.head),
|
|
375
|
-
Effect.map((row) => row.timestamp as TimestampString),
|
|
376
|
-
Effect.catchTag("NoSuchElementException", () => Effect.succeed(null)),
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
if (timestamp == null || timestamp < message.timestamp) {
|
|
380
|
-
const { millis } = unsafeTimestampFromString(message.timestamp);
|
|
381
|
-
yield* _(upsertValueIntoTableRowColumn(message, messages, millis));
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (timestamp == null || timestamp !== message.timestamp) {
|
|
385
|
-
const { changes } = yield* _(
|
|
386
|
-
sqlite.exec({
|
|
387
|
-
...Sql.insertIntoMessagesIfNew,
|
|
388
|
-
parameters: [
|
|
389
|
-
message.timestamp,
|
|
390
|
-
message.table,
|
|
391
|
-
message.row,
|
|
392
|
-
message.column,
|
|
393
|
-
message.value,
|
|
394
|
-
],
|
|
395
|
-
}),
|
|
396
|
-
);
|
|
397
|
-
if (changes > 0) {
|
|
398
|
-
const timestamp = unsafeTimestampFromString(message.timestamp);
|
|
399
|
-
merkleTree = insertIntoMerkleTree(timestamp)(merkleTree);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return merkleTree;
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const writeTimestampAndMerkleTree = ({
|
|
408
|
-
timestamp,
|
|
409
|
-
merkleTree,
|
|
410
|
-
}: TimestampAndMerkleTree): Effect.Effect<void, never, Sqlite> =>
|
|
411
|
-
Effect.flatMap(Sqlite, (sqlite) =>
|
|
412
|
-
sqlite.exec({
|
|
413
|
-
...Sql.updateOwnerTimestampAndMerkleTree,
|
|
414
|
-
parameters: [
|
|
415
|
-
merkleTreeToString(merkleTree),
|
|
416
|
-
timestampToString(timestamp),
|
|
417
|
-
],
|
|
418
|
-
}),
|
|
419
|
-
);
|
|
420
|
-
|
|
421
|
-
const mutate = ({
|
|
422
|
-
mutations,
|
|
423
|
-
queries,
|
|
424
|
-
}: DbWorkerInputMutate): Effect.Effect<
|
|
425
|
-
void,
|
|
426
|
-
| TimestampDriftError
|
|
427
|
-
| TimestampCounterOverflowError
|
|
428
|
-
| TimestampTimeOutOfRangeError,
|
|
429
|
-
| Sqlite
|
|
430
|
-
| Owner
|
|
431
|
-
| Time
|
|
432
|
-
| Config
|
|
433
|
-
| RowsStore
|
|
434
|
-
| DbWorkerOnMessage
|
|
435
|
-
| SyncWorkerPostMessage
|
|
436
|
-
> =>
|
|
437
|
-
Effect.gen(function* (_) {
|
|
438
|
-
const [toSync, localOnly] = ReadonlyArray.partition(mutations, (item) =>
|
|
439
|
-
item.table.startsWith("_"),
|
|
440
|
-
);
|
|
441
|
-
const [toUpsert, toDelete] = ReadonlyArray.partition(localOnly, (item) =>
|
|
442
|
-
mutationsToNewMessages([item]).some(
|
|
443
|
-
(message) => message.column === "isDeleted" && message.value === 1,
|
|
444
|
-
),
|
|
445
|
-
).map(mutationsToNewMessages);
|
|
446
|
-
|
|
447
|
-
const time = yield* _(Time);
|
|
448
|
-
for (const messageToUpsert of toUpsert) {
|
|
449
|
-
const now = yield* _(time.now);
|
|
450
|
-
yield* _(upsertValueIntoTableRowColumn(messageToUpsert, toUpsert, now));
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const { exec } = yield* _(Sqlite);
|
|
454
|
-
yield* _(
|
|
455
|
-
Effect.forEach(toDelete, ({ table, row }) =>
|
|
456
|
-
exec({
|
|
457
|
-
sql: `delete from "${table}" where "id" = ?;`,
|
|
458
|
-
parameters: [row],
|
|
459
|
-
}),
|
|
460
|
-
),
|
|
461
|
-
);
|
|
462
|
-
|
|
463
|
-
if (toSync.length > 0) {
|
|
464
|
-
let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
|
|
465
|
-
|
|
466
|
-
const messages = yield* _(
|
|
467
|
-
mutationsToNewMessages(toSync),
|
|
468
|
-
Effect.forEach((message) =>
|
|
469
|
-
Effect.map(sendTimestamp(timestamp), (nextTimestamp): Message => {
|
|
470
|
-
timestamp = nextTimestamp;
|
|
471
|
-
return { ...message, timestamp: timestampToString(timestamp) };
|
|
472
|
-
}),
|
|
473
|
-
),
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
merkleTree = yield* _(applyMessages({ merkleTree, messages }));
|
|
477
|
-
|
|
478
|
-
yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
|
|
479
|
-
|
|
480
|
-
(yield* _(SyncWorkerPostMessage))({
|
|
481
|
-
_tag: "sync",
|
|
482
|
-
syncUrl: (yield* _(Config)).syncUrl,
|
|
483
|
-
messages,
|
|
484
|
-
timestamp,
|
|
485
|
-
merkleTree,
|
|
486
|
-
owner: yield* _(Owner),
|
|
487
|
-
syncLoopCount: 0,
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const onCompleteIds = ReadonlyArray.filterMap(mutations, (item) =>
|
|
492
|
-
Option.fromNullable(item.onCompleteId),
|
|
493
|
-
);
|
|
494
|
-
if (queries.length > 0 || onCompleteIds.length > 0)
|
|
495
|
-
yield* _(query({ queries, onCompleteIds }));
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
const handleSyncResponse = ({
|
|
499
|
-
messages,
|
|
500
|
-
...response
|
|
501
|
-
}: SyncWorkerOutputSyncResponse): Effect.Effect<
|
|
502
|
-
void,
|
|
503
|
-
TimestampError,
|
|
504
|
-
Sqlite | Time | Config | DbWorkerOnMessage | SyncWorkerPostMessage | Owner
|
|
505
|
-
> =>
|
|
506
|
-
Effect.gen(function* (_) {
|
|
507
|
-
let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
|
|
508
|
-
|
|
509
|
-
const dbWorkerOnMessage = yield* _(DbWorkerOnMessage);
|
|
510
|
-
if (messages.length > 0) {
|
|
511
|
-
for (const message of messages)
|
|
512
|
-
timestamp = yield* _(
|
|
513
|
-
unsafeTimestampFromString(message.timestamp),
|
|
514
|
-
(remote) => receiveTimestamp({ local: timestamp, remote }),
|
|
515
|
-
);
|
|
516
|
-
merkleTree = yield* _(applyMessages({ merkleTree, messages }));
|
|
517
|
-
yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
|
|
518
|
-
dbWorkerOnMessage({ _tag: "onReceive" });
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const diff = diffMerkleTrees(response.merkleTree, merkleTree);
|
|
522
|
-
|
|
523
|
-
const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
|
|
524
|
-
|
|
525
|
-
if (Option.isNone(diff)) {
|
|
526
|
-
syncWorkerPostMessage({ _tag: "syncCompleted" });
|
|
527
|
-
dbWorkerOnMessage({
|
|
528
|
-
_tag: "onSyncState",
|
|
529
|
-
state: {
|
|
530
|
-
_tag: "SyncStateIsSynced",
|
|
531
|
-
time: yield* _(Time.pipe(Effect.flatMap((time) => time.now))),
|
|
532
|
-
},
|
|
533
|
-
});
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const sqlite = yield* _(Sqlite);
|
|
538
|
-
const config = yield* _(Config);
|
|
539
|
-
const owner = yield* _(Owner);
|
|
540
|
-
|
|
541
|
-
const messagesToSync = yield* _(
|
|
542
|
-
sqlite.exec({
|
|
543
|
-
...Sql.selectMessagesToSync,
|
|
544
|
-
parameters: [timestampToString(makeSyncTimestamp(diff.value))],
|
|
545
|
-
}),
|
|
546
|
-
Effect.map(({ rows }) => rows as unknown as ReadonlyArray<Message>),
|
|
547
|
-
);
|
|
548
|
-
|
|
549
|
-
if (response.syncLoopCount > 100) {
|
|
550
|
-
// TODO: dbWorkerOnMessage({ _tag: "onError" });
|
|
551
|
-
// eslint-disable-next-line no-console
|
|
552
|
-
console.error("Evolu: syncLoopCount > 100");
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
syncWorkerPostMessage({
|
|
557
|
-
_tag: "sync",
|
|
558
|
-
syncUrl: config.syncUrl,
|
|
559
|
-
messages: messagesToSync,
|
|
560
|
-
timestamp,
|
|
561
|
-
merkleTree,
|
|
562
|
-
owner,
|
|
563
|
-
syncLoopCount: response.syncLoopCount + 1,
|
|
564
|
-
});
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
const sync = ({
|
|
568
|
-
queries,
|
|
569
|
-
}: DbWorkerInputSync): Effect.Effect<
|
|
570
|
-
void,
|
|
571
|
-
never,
|
|
572
|
-
| Sqlite
|
|
573
|
-
| Config
|
|
574
|
-
| DbWorkerOnMessage
|
|
575
|
-
| SyncWorkerPostMessage
|
|
576
|
-
| Owner
|
|
577
|
-
| RowsStore
|
|
578
|
-
> =>
|
|
579
|
-
Effect.gen(function* (_) {
|
|
580
|
-
if (queries.length > 0) yield* _(query({ queries }));
|
|
581
|
-
const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
|
|
582
|
-
syncWorkerPostMessage({
|
|
583
|
-
_tag: "sync",
|
|
584
|
-
...(yield* _(readTimestampAndMerkleTree)),
|
|
585
|
-
syncUrl: (yield* _(Config)).syncUrl,
|
|
586
|
-
owner: yield* _(Owner),
|
|
587
|
-
messages: [],
|
|
588
|
-
syncLoopCount: 0,
|
|
589
|
-
});
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
const reset = (
|
|
593
|
-
input: DbWorkerInputReset,
|
|
594
|
-
): Effect.Effect<
|
|
595
|
-
void,
|
|
596
|
-
never,
|
|
597
|
-
Sqlite | Bip39 | NanoIdGenerator | DbWorkerOnMessage
|
|
598
|
-
> =>
|
|
599
|
-
Effect.gen(function* (_) {
|
|
600
|
-
yield* _(dropAllTables);
|
|
601
|
-
if (input.mnemonic) yield* _(lazyInit(input.mnemonic));
|
|
602
|
-
const onMessage = yield* _(DbWorkerOnMessage);
|
|
603
|
-
onMessage({ _tag: "onResetOrRestore" });
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
export const DbWorkerCommonLive = Layer.effect(
|
|
607
|
-
DbWorker,
|
|
608
|
-
Effect.gen(function* (_) {
|
|
609
|
-
const syncWorker = yield* _(SyncWorker);
|
|
610
|
-
|
|
611
|
-
const onError = (error: EvoluError): Effect.Effect<void> =>
|
|
612
|
-
Effect.sync(() => {
|
|
613
|
-
dbWorker.onMessage({ _tag: "onError", error });
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
syncWorker.onMessage = (output): void => {
|
|
617
|
-
switch (output._tag) {
|
|
618
|
-
case "UnexpectedError":
|
|
619
|
-
onError(output).pipe(Effect.runSync);
|
|
620
|
-
break;
|
|
621
|
-
case "SyncWorkerOutputSyncResponse":
|
|
622
|
-
dbWorker.postMessage(output);
|
|
623
|
-
break;
|
|
624
|
-
default:
|
|
625
|
-
dbWorker.onMessage({ _tag: "onSyncState", state: output });
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
const runContext = Context.empty().pipe(
|
|
630
|
-
Context.add(Sqlite, yield* _(Sqlite)),
|
|
631
|
-
Context.add(Bip39, yield* _(Bip39)),
|
|
632
|
-
Context.add(NanoIdGenerator, yield* _(NanoIdGenerator)),
|
|
633
|
-
Context.add(DbWorkerOnMessage, (output) => {
|
|
634
|
-
dbWorker.onMessage(output);
|
|
635
|
-
}),
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
const run = (
|
|
639
|
-
effect: Effect.Effect<
|
|
640
|
-
void,
|
|
641
|
-
EvoluError,
|
|
642
|
-
Sqlite | Bip39 | NanoIdGenerator | DbWorkerOnMessage
|
|
643
|
-
>,
|
|
644
|
-
): Promise<void> =>
|
|
645
|
-
effect.pipe(
|
|
646
|
-
Effect.catchAllDefect(makeUnexpectedError),
|
|
647
|
-
transaction,
|
|
648
|
-
Effect.catchAll(onError),
|
|
649
|
-
Effect.provide(runContext),
|
|
650
|
-
Effect.runPromise,
|
|
651
|
-
);
|
|
652
|
-
|
|
653
|
-
type HandleInput = (input: DbWorkerInput) => Promise<void>;
|
|
654
|
-
|
|
655
|
-
/** If init fails, we have to allow reset at least. */
|
|
656
|
-
const handleInputForInitFail: HandleInput = (input): Promise<void> => {
|
|
657
|
-
if (input._tag !== "reset") return Promise.resolve(undefined);
|
|
658
|
-
return reset(input).pipe(run);
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
const makeHandleInputForInitSuccess = (
|
|
662
|
-
config: Config,
|
|
663
|
-
owner: Owner,
|
|
664
|
-
): HandleInput => {
|
|
665
|
-
let skipAllBecauseOfReset = false;
|
|
666
|
-
|
|
667
|
-
const layer = Layer.mergeAll(
|
|
668
|
-
ConfigLive(config),
|
|
669
|
-
Layer.succeed(Owner, owner),
|
|
670
|
-
Layer.succeed(SyncWorkerPostMessage, syncWorker.postMessage),
|
|
671
|
-
RowsStoreLive,
|
|
672
|
-
TimeLive,
|
|
673
|
-
).pipe(Layer.memoize, Effect.scoped, Effect.runSync);
|
|
674
|
-
|
|
675
|
-
return (input) => {
|
|
676
|
-
if (skipAllBecauseOfReset) return Promise.resolve(undefined);
|
|
677
|
-
return Match.value(input).pipe(
|
|
678
|
-
Match.tagsExhaustive({
|
|
679
|
-
init: () =>
|
|
680
|
-
makeUnexpectedError(new Error("init must be called once")),
|
|
681
|
-
query,
|
|
682
|
-
mutate,
|
|
683
|
-
sync,
|
|
684
|
-
reset: (input) => {
|
|
685
|
-
skipAllBecauseOfReset = true;
|
|
686
|
-
return reset(input);
|
|
687
|
-
},
|
|
688
|
-
ensureSchema,
|
|
689
|
-
SyncWorkerOutputSyncResponse: handleSyncResponse,
|
|
690
|
-
}),
|
|
691
|
-
Effect.provide(layer),
|
|
692
|
-
run,
|
|
693
|
-
);
|
|
694
|
-
};
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
let handleInput: HandleInput = (input) => {
|
|
698
|
-
if (input._tag !== "init")
|
|
699
|
-
return run(makeUnexpectedError(new Error("init must be called first")));
|
|
700
|
-
handleInput = handleInputForInitFail;
|
|
701
|
-
return init.pipe(
|
|
702
|
-
Effect.map((owner) => {
|
|
703
|
-
dbWorker.onMessage({ _tag: "onOwner", owner });
|
|
704
|
-
handleInput = makeHandleInputForInitSuccess(input.config, owner);
|
|
705
|
-
}),
|
|
706
|
-
run,
|
|
707
|
-
);
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
const dbWorkerLock = yield* _(DbWorkerLock);
|
|
711
|
-
|
|
712
|
-
const dbWorker: DbWorker = {
|
|
713
|
-
postMessage: (input) => {
|
|
714
|
-
dbWorkerLock(() => handleInput(input));
|
|
715
|
-
},
|
|
716
|
-
onMessage: Function.constVoid,
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
return dbWorker;
|
|
720
|
-
}),
|
|
721
|
-
);
|