@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/dist/src/Db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"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"}
|
package/dist/src/Db.js
CHANGED
|
@@ -1,150 +1,121 @@
|
|
|
1
|
-
import * as AST from "@effect/schema/AST";
|
|
2
1
|
import * as S from "@effect/schema/Schema";
|
|
3
|
-
import
|
|
2
|
+
import * as Arr from "effect/Array";
|
|
3
|
+
import * as Console from "effect/Console";
|
|
4
4
|
import * as Context from "effect/Context";
|
|
5
|
+
import * as Deferred from "effect/Deferred";
|
|
5
6
|
import * as Effect from "effect/Effect";
|
|
6
|
-
import
|
|
7
|
-
import { pipe } from "effect/Function";
|
|
8
|
-
import * as Layer from "effect/Layer";
|
|
7
|
+
import { constVoid, pipe } from "effect/Function";
|
|
9
8
|
import * as Option from "effect/Option";
|
|
10
9
|
import * as Predicate from "effect/Predicate";
|
|
11
|
-
import * as
|
|
12
|
-
import * as
|
|
10
|
+
import * as Ref from "effect/Ref";
|
|
11
|
+
import * as Schedule from "effect/Schedule";
|
|
13
12
|
import * as String from "effect/String";
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* const TodoId = id("Todo");
|
|
31
|
-
* type TodoId = S.Schema.Type<typeof TodoId>;
|
|
32
|
-
*
|
|
33
|
-
* const TodoTable = table({
|
|
34
|
-
* id: TodoId,
|
|
35
|
-
* title: NonEmptyString1000,
|
|
36
|
-
* isCompleted: S.nullable(SqliteBoolean),
|
|
37
|
-
* });
|
|
38
|
-
* type TodoTable = S.Schema.Type<typeof TodoTable>;
|
|
39
|
-
*/
|
|
40
|
-
export const table = (fields) => S.struct(fields).pipe(S.extend(ReservedColumns));
|
|
41
|
-
const ReservedColumns = S.struct({
|
|
42
|
-
createdAt: SqliteDate,
|
|
43
|
-
updatedAt: SqliteDate,
|
|
44
|
-
isDeleted: SqliteBoolean,
|
|
13
|
+
import * as SynchronizedRef from "effect/SynchronizedRef";
|
|
14
|
+
import { Time, diffMerkleTrees, initialMerkleTree, insertIntoMerkleTree, makeInitialTimestamp, makeSyncTimestamp, merkleTreeToString, receiveTimestamp, sendTimestamp, timestampToString, unsafeTimestampFromString, } from "./Crdt.js";
|
|
15
|
+
import { Bip39, NanoIdGenerator } from "./Crypto.js";
|
|
16
|
+
import { makePatches } from "./Diff.js";
|
|
17
|
+
import { ensureTransferableError, makeUnexpectedError, } from "./Error.js";
|
|
18
|
+
import { cast } from "./Model.js";
|
|
19
|
+
import { Owner, makeOwner } from "./Owner.js";
|
|
20
|
+
import { SyncLock } from "./Platform.js";
|
|
21
|
+
import * as Sql from "./Sql.js";
|
|
22
|
+
import { Sqlite, SqliteFactory, drawSqliteQueryPlan, isJsonObjectOrArray, } from "./Sqlite.js";
|
|
23
|
+
import { Sync, SyncFactory, } from "./Sync.js";
|
|
24
|
+
export const Index = S.Struct({
|
|
25
|
+
name: S.String,
|
|
26
|
+
sql: S.String,
|
|
45
27
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
28
|
+
export class DbFactory extends Context.Tag("DbFactory")() {
|
|
29
|
+
}
|
|
30
|
+
export const createDb = Effect.gen(function* () {
|
|
31
|
+
const { createSqlite } = yield* SqliteFactory;
|
|
32
|
+
const { createSync } = yield* SyncFactory;
|
|
33
|
+
const initContext = Context.empty().pipe(Context.add(Bip39, yield* Bip39), Context.add(NanoIdGenerator, yield* NanoIdGenerator), Context.add(Time, yield* Time), Context.add(SyncLock, yield* SyncLock));
|
|
34
|
+
const afterInitContext = yield* Deferred.make();
|
|
35
|
+
const afterInit = (options) => (effect) => Effect.flatMap(Deferred.await(afterInitContext), (context) => Effect.flatMap(Sqlite, (sqlite) => sqlite.transaction(options.transaction)(effect)).pipe(Effect.provide(context)));
|
|
36
|
+
const queryRowsRef = yield* SynchronizedRef.make(new Map());
|
|
37
|
+
const db = {
|
|
38
|
+
init: (schema, initialData, onError, onSyncStateChange, onReceive) => Effect.gen(function* () {
|
|
39
|
+
yield* Effect.logDebug(["Db init", { schema }]);
|
|
40
|
+
const sqlite = yield* createSqlite;
|
|
41
|
+
const contextWithSqlite = Context.add(initContext, Sqlite, sqlite);
|
|
42
|
+
const owner = yield* getSchema.pipe(Effect.tap(ensureSchema(schema)), Effect.flatMap((currentSchema) => {
|
|
43
|
+
if (currentSchema.tables.map((t) => t.name).includes("evolu_owner"))
|
|
44
|
+
return readOwner;
|
|
45
|
+
return createOwner().pipe(Effect.tap(applyMutations(initialData)));
|
|
46
|
+
}), sqlite.transaction("exclusive"), Effect.provide(contextWithSqlite));
|
|
47
|
+
const sync = yield* createSync.pipe(Effect.provide(initContext));
|
|
48
|
+
yield* sync.init(owner);
|
|
49
|
+
Deferred.unsafeDone(afterInitContext, Effect.succeed(contextWithSqlite.pipe(Context.add(Owner, owner), Context.add(Sync, sync), Context.add(Callbacks, { onError, onSyncStateChange, onReceive }))));
|
|
50
|
+
return owner;
|
|
51
|
+
}),
|
|
52
|
+
loadQueries: (queries) => Effect.logDebug(["Db loadQueries", { queries }]).pipe(Effect.zipRight(loadQueries(queries, queryRowsRef)), afterInit({ transaction: "shared" })),
|
|
53
|
+
mutate: (mutations, queriesToRefresh) => Effect.gen(function* () {
|
|
54
|
+
yield* Effect.logDebug(["Db mutate", { mutations, queriesToRefresh }]);
|
|
55
|
+
const time = yield* Time;
|
|
56
|
+
const sqlite = yield* Sqlite;
|
|
57
|
+
const [toSyncMutations, localOnlyMutations] = Arr.partition(mutations,
|
|
58
|
+
/** Table name starting with '_' is local only (not synced). */
|
|
59
|
+
(mutation) => mutation.table.startsWith("_"));
|
|
60
|
+
for (const mutation of localOnlyMutations) {
|
|
61
|
+
const isDeleteMutation = mutationToNewMessages(mutation).some(({ column, value }) => column === "isDeleted" && value === 1);
|
|
62
|
+
if (isDeleteMutation) {
|
|
63
|
+
yield* sqlite.exec({
|
|
64
|
+
sql: `delete from "${mutation.table}" where "id" = ?;`,
|
|
65
|
+
parameters: [mutation.id],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const messages = mutationToNewMessages(mutation);
|
|
70
|
+
for (const message of messages) {
|
|
71
|
+
const now = yield* time.now;
|
|
72
|
+
yield* upsertValueIntoTableRowColumn(message, messages, now);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (toSyncMutations.length > 0) {
|
|
77
|
+
yield* Effect.tap(applyMutations(toSyncMutations), forkSync);
|
|
78
|
+
}
|
|
79
|
+
return yield* loadQueries(queriesToRefresh, queryRowsRef);
|
|
80
|
+
}).pipe(afterInit({ transaction: "exclusive" })),
|
|
81
|
+
resetOwner: () => Effect.logTrace("Db resetOwner").pipe(Effect.tap(dropAllTables), afterInit({ transaction: "last" })),
|
|
82
|
+
restoreOwner: (schema, mnemonic) => Effect.logTrace("Db restoreOwner").pipe(Effect.tap(dropAllTables), Effect.tap(Effect.flatMap(getSchema, ensureSchema(schema))), Effect.tap(createOwner(mnemonic)), afterInit({ transaction: "last" })),
|
|
83
|
+
ensureSchema: (schema) => getSchema.pipe(Effect.flatMap(ensureSchema(schema)), afterInit({ transaction: "exclusive" })),
|
|
84
|
+
sync: (queriesToRefresh) => Effect.logDebug(["Db sync", { queriesToRefresh }]).pipe(Effect.zipRight(forkSync()), Effect.zipRight(loadQueries(queriesToRefresh, queryRowsRef)), afterInit({ transaction: "shared" })),
|
|
85
|
+
// TODO:
|
|
86
|
+
// dispose: () =>
|
|
87
|
+
// Effect.logTrace("Db dispose").pipe(
|
|
88
|
+
// Effect.tap(Scope.close(scope, Exit.succeed("Db disposed"))),
|
|
89
|
+
// afterInit({ transaction: "last" }),
|
|
90
|
+
// ),
|
|
84
91
|
};
|
|
85
|
-
|
|
86
|
-
const _emptyRows = [];
|
|
87
|
-
export const emptyRows = () => _emptyRows;
|
|
88
|
-
const queryResultCache = new WeakMap();
|
|
89
|
-
export const queryResultFromRows = (rows) => {
|
|
90
|
-
let queryResult = queryResultCache.get(rows);
|
|
91
|
-
if (queryResult == null) {
|
|
92
|
-
queryResult = { rows, row: rows[0] };
|
|
93
|
-
queryResultCache.set(rows, queryResult);
|
|
94
|
-
}
|
|
95
|
-
return queryResult;
|
|
96
|
-
};
|
|
97
|
-
// TODO: https://discord.com/channels/795981131316985866/1218626687546294386/1218796529725476935
|
|
98
|
-
// https://github.com/Effect-TS/schema/releases/tag/v0.18.0
|
|
99
|
-
const getPropertySignatures = (schema) => {
|
|
100
|
-
const out = {};
|
|
101
|
-
const propertySignatures = AST.getPropertySignatures(schema.ast);
|
|
102
|
-
for (let i = 0; i < propertySignatures.length; i++) {
|
|
103
|
-
const propertySignature = propertySignatures[i];
|
|
104
|
-
out[propertySignature.name] = make(propertySignature.type);
|
|
105
|
-
}
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
107
|
-
return out;
|
|
108
|
-
};
|
|
109
|
-
export const schemaToTables = (schema) => pipe(getPropertySignatures(schema), ReadonlyRecord.toEntries, ReadonlyArray.map(([name, schema]) => ({
|
|
110
|
-
name,
|
|
111
|
-
columns: Object.keys(getPropertySignatures(schema)),
|
|
112
|
-
})));
|
|
113
|
-
export const transaction = (effect) => Effect.flatMap(Sqlite, (sqlite) => Effect.acquireUseRelease(sqlite.exec({ sql: "begin" }), () => effect, (_, exit) => Exit.isFailure(exit)
|
|
114
|
-
? sqlite.exec({ sql: "rollback" })
|
|
115
|
-
: sqlite.exec({ sql: "end" })));
|
|
116
|
-
export const SqliteNoSuchTableOrColumnError = S.struct({
|
|
117
|
-
message: S.union(S.string.pipe(S.includes("no such table")), S.string.pipe(S.includes("no such column")), S.string.pipe(S.includes("has no column"))),
|
|
92
|
+
return db;
|
|
118
93
|
});
|
|
119
|
-
export const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
94
|
+
export const Callbacks = Context.GenericTag("Callbacks");
|
|
95
|
+
const loadQueries = (queries, queryRowsRef) => Effect.gen(function* () {
|
|
96
|
+
const sqlite = yield* Sqlite;
|
|
97
|
+
const previousState = yield* SynchronizedRef.getAndUpdateEffect(queryRowsRef, (previousState) => Effect.map(Effect.forEach(queries, (query) => {
|
|
98
|
+
const sqliteQuery = deserializeQuery(query);
|
|
99
|
+
return sqlite.exec(sqliteQuery).pipe(Effect.tap(maybeExplainQueryPlan(sqliteQuery)), Effect.map(({ rows }) => [query, rows]));
|
|
100
|
+
}), (queriesRows) => new Map([...previousState, ...queriesRows])));
|
|
101
|
+
const currentState = yield* SynchronizedRef.get(queryRowsRef);
|
|
102
|
+
return queries.map((query) => ({
|
|
103
|
+
query,
|
|
104
|
+
patches: makePatches(previousState.get(query), currentState.get(query) || []),
|
|
127
105
|
}));
|
|
128
|
-
yield* _(Effect.all([
|
|
129
|
-
sqlite.exec(createMessageTable),
|
|
130
|
-
sqlite.exec(createMessageTableIndex),
|
|
131
|
-
sqlite.exec(createOwnerTable),
|
|
132
|
-
sqlite.exec({
|
|
133
|
-
...insertOwner,
|
|
134
|
-
parameters: [
|
|
135
|
-
owner.id,
|
|
136
|
-
owner.mnemonic,
|
|
137
|
-
owner.encryptionKey,
|
|
138
|
-
timestampToString(initialTimestampString),
|
|
139
|
-
merkleTreeToString(initialMerkleTree),
|
|
140
|
-
],
|
|
141
|
-
}),
|
|
142
|
-
]));
|
|
143
|
-
return owner;
|
|
144
106
|
});
|
|
107
|
+
const maybeExplainQueryPlan = (sqliteQuery) => {
|
|
108
|
+
if (!sqliteQuery.options?.logExplainQueryPlan)
|
|
109
|
+
return Effect.void;
|
|
110
|
+
return Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec({
|
|
111
|
+
...sqliteQuery,
|
|
112
|
+
sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
|
|
113
|
+
})), Effect.tap(Console.log("ExplainQueryPlan", sqliteQuery)), Effect.tap(({ rows }) => Console.log(drawSqliteQueryPlan(rows))), Effect.map(constVoid));
|
|
114
|
+
};
|
|
145
115
|
const getSchema = Effect.gen(function* (_) {
|
|
146
|
-
|
|
147
|
-
const
|
|
116
|
+
yield* Effect.logTrace("Db getSchema");
|
|
117
|
+
const sqlite = yield* Sqlite;
|
|
118
|
+
const tables = yield* Effect.map(sqlite.exec({
|
|
148
119
|
// https://til.simonwillison.net/sqlite/list-all-columns-in-a-database
|
|
149
120
|
sql: `
|
|
150
121
|
select
|
|
@@ -153,8 +124,8 @@ select
|
|
|
153
124
|
from
|
|
154
125
|
sqlite_master
|
|
155
126
|
join pragma_table_info(sqlite_master.name) as table_info
|
|
156
|
-
`.trim(),
|
|
157
|
-
}),
|
|
127
|
+
`.trim(),
|
|
128
|
+
}), ({ rows }) => {
|
|
158
129
|
const map = new Map();
|
|
159
130
|
rows.forEach((row) => {
|
|
160
131
|
const { tableName, columnName } = row;
|
|
@@ -162,12 +133,12 @@ from
|
|
|
162
133
|
map.set(tableName, []);
|
|
163
134
|
map.get(tableName)?.push(columnName);
|
|
164
135
|
});
|
|
165
|
-
return Array.from(map, ([name, columns]) => ({
|
|
136
|
+
return globalThis.Array.from(map, ([name, columns]) => ({
|
|
166
137
|
name,
|
|
167
138
|
columns,
|
|
168
139
|
}));
|
|
169
|
-
})
|
|
170
|
-
const indexes = yield*
|
|
140
|
+
});
|
|
141
|
+
const indexes = yield* Effect.map(sqlite.exec({
|
|
171
142
|
sql: `
|
|
172
143
|
select
|
|
173
144
|
name, sql
|
|
@@ -178,7 +149,7 @@ where
|
|
|
178
149
|
name not like 'sqlite_%' and
|
|
179
150
|
name not like 'index_evolu_%'
|
|
180
151
|
`.trim(),
|
|
181
|
-
}),
|
|
152
|
+
}), (result) => Arr.map(result.rows, (row) => ({
|
|
182
153
|
name: row.name,
|
|
183
154
|
/**
|
|
184
155
|
* SQLite returns "CREATE INDEX" for "create index" for some reason.
|
|
@@ -186,58 +157,285 @@ where
|
|
|
186
157
|
* for `indexEquivalence` manually.
|
|
187
158
|
*/
|
|
188
159
|
sql: row.sql.replace("CREATE INDEX", "create index"),
|
|
189
|
-
})))
|
|
160
|
+
})));
|
|
190
161
|
return { tables, indexes };
|
|
191
162
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const currentSchema = yield* _(getSchema);
|
|
163
|
+
const ensureSchema = (newSchema) => (currentSchema) => Effect.gen(function* () {
|
|
164
|
+
yield* Effect.logTrace("Db ensureSchema");
|
|
195
165
|
const sql = [];
|
|
196
|
-
|
|
166
|
+
newSchema.tables.forEach((table) => {
|
|
197
167
|
const currentTable = currentSchema.tables.find((t) => t.name === table.name);
|
|
198
168
|
if (!currentTable) {
|
|
199
169
|
sql.push(`
|
|
200
|
-
create table ${table.name} (
|
|
201
|
-
|
|
202
|
-
|
|
170
|
+
create table ${table.name} (
|
|
171
|
+
"id" text primary key,
|
|
172
|
+
${table.columns
|
|
203
173
|
.filter((c) => c !== "id")
|
|
204
174
|
// "A column with affinity BLOB does not prefer one storage class over another
|
|
205
175
|
// and no attempt is made to coerce data from one storage class into another."
|
|
206
176
|
// https://www.sqlite.org/datatype3.html
|
|
207
177
|
.map((name) => `"${name}" blob`)
|
|
208
178
|
.join(", ")}
|
|
209
|
-
);`.trim());
|
|
179
|
+
);`.trim());
|
|
210
180
|
}
|
|
211
181
|
else {
|
|
212
|
-
|
|
182
|
+
Arr.differenceWith(String.Equivalence)(table.columns, currentTable.columns).forEach((newColumn) => {
|
|
213
183
|
sql.push(`alter table "${table.name}" add column "${newColumn}" blob;`);
|
|
214
184
|
});
|
|
215
185
|
}
|
|
216
186
|
});
|
|
217
187
|
// Remove old indexes.
|
|
218
|
-
|
|
188
|
+
Arr.differenceWith(indexEquivalence)(currentSchema.indexes, Arr.intersectionWith(indexEquivalence)(currentSchema.indexes, newSchema.indexes)).forEach((indexToDrop) => {
|
|
219
189
|
sql.push(`drop index "${indexToDrop.name}";`);
|
|
220
190
|
});
|
|
221
191
|
// Add new indexes.
|
|
222
|
-
|
|
192
|
+
Arr.differenceWith(indexEquivalence)(newSchema.indexes, currentSchema.indexes).forEach((newIndex) => {
|
|
223
193
|
sql.push(`${newIndex.sql};`);
|
|
224
194
|
});
|
|
225
|
-
if (sql.length > 0)
|
|
226
|
-
yield*
|
|
227
|
-
|
|
228
|
-
|
|
195
|
+
if (sql.length > 0) {
|
|
196
|
+
const sqlite = yield* Sqlite;
|
|
197
|
+
yield* sqlite.exec({ sql: sql.join("\n") });
|
|
198
|
+
}
|
|
229
199
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
200
|
+
const indexEquivalence = (self, that) => self.name === that.name && self.sql === that.sql;
|
|
201
|
+
const readOwner = Effect.logTrace("Db readOwner").pipe(Effect.zipRight(Sqlite), Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwner)), Effect.map(({ rows: [row] }) => ({
|
|
202
|
+
id: row.id,
|
|
203
|
+
mnemonic: row.mnemonic,
|
|
204
|
+
encryptionKey: row.encryptionKey,
|
|
205
|
+
})));
|
|
206
|
+
const createOwner = (mnemonic) => Effect.logTrace("Db createOwner").pipe(Effect.zipRight(Effect.all([makeOwner(mnemonic), Sqlite, makeInitialTimestamp])), Effect.tap(([owner, sqlite, initialTimestampString]) => Effect.all([
|
|
207
|
+
sqlite.exec(Sql.createMessageTable),
|
|
208
|
+
sqlite.exec(Sql.createMessageTableIndex),
|
|
209
|
+
sqlite.exec(Sql.createOwnerTable),
|
|
210
|
+
sqlite.exec({
|
|
211
|
+
...Sql.insertOwner,
|
|
212
|
+
parameters: [
|
|
213
|
+
owner.id,
|
|
214
|
+
owner.mnemonic,
|
|
215
|
+
owner.encryptionKey,
|
|
216
|
+
timestampToString(initialTimestampString),
|
|
217
|
+
merkleTreeToString(initialMerkleTree),
|
|
218
|
+
],
|
|
219
|
+
}),
|
|
220
|
+
])), Effect.map(([owner]) => owner));
|
|
221
|
+
const applyMutations = (mutations) => Effect.gen(function* (_) {
|
|
222
|
+
const { timestamp, merkleTree } = yield* readTimestampAndMerkleTree;
|
|
223
|
+
const [nextTimestamp, messages] = yield* Effect.mapAccum(mutations.flatMap(mutationToNewMessages), timestamp, (currentTimestamp, newMessage) => Effect.map(sendTimestamp(currentTimestamp), (nextTimestamp) => {
|
|
224
|
+
const message = {
|
|
225
|
+
...newMessage,
|
|
226
|
+
timestamp: timestampToString(nextTimestamp),
|
|
227
|
+
};
|
|
228
|
+
return [nextTimestamp, message];
|
|
229
|
+
}));
|
|
230
|
+
const nextMerkleTree = yield* applyMessages(merkleTree, messages);
|
|
231
|
+
yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
|
|
232
|
+
return messages;
|
|
241
233
|
});
|
|
242
|
-
|
|
243
|
-
|
|
234
|
+
const readTimestampAndMerkleTree = Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree)), Effect.map(({ rows: [{ timestamp, merkleTree }] }) => ({
|
|
235
|
+
timestamp: unsafeTimestampFromString(timestamp),
|
|
236
|
+
merkleTree: merkleTree,
|
|
237
|
+
})));
|
|
238
|
+
const mutationToNewMessages = (mutation) => pipe(Object.entries(mutation.values), Arr.filterMap(([column, value]) =>
|
|
239
|
+
// The value can be undefined if exactOptionalPropertyTypes isn't true.
|
|
240
|
+
// Don't insert nulls because null is the default value.
|
|
241
|
+
value === undefined || (mutation.isInsert && value == null)
|
|
242
|
+
? Option.none()
|
|
243
|
+
: Option.some([column, value])), Arr.map(([column, value]) => ({
|
|
244
|
+
table: mutation.table,
|
|
245
|
+
row: mutation.id,
|
|
246
|
+
column,
|
|
247
|
+
value: typeof value === "boolean"
|
|
248
|
+
? cast(value)
|
|
249
|
+
: value instanceof Date
|
|
250
|
+
? cast(value)
|
|
251
|
+
: value,
|
|
252
|
+
})));
|
|
253
|
+
const applyMessages = (merkleTree, messages) => Effect.gen(function* () {
|
|
254
|
+
const sqlite = yield* Sqlite;
|
|
255
|
+
for (const message of messages) {
|
|
256
|
+
const messageTimestamp = unsafeTimestampFromString(message.timestamp);
|
|
257
|
+
const lastTimestamp = yield* Effect.map(sqlite.exec({
|
|
258
|
+
...Sql.selectLastTimestampForTableRowColumn,
|
|
259
|
+
parameters: [message.table, message.row, message.column, 1],
|
|
260
|
+
}), ({ rows }) => rows.length > 0 ? rows[0].timestamp : null);
|
|
261
|
+
if (lastTimestamp == null || lastTimestamp < message.timestamp) {
|
|
262
|
+
yield* upsertValueIntoTableRowColumn(message, messages, messageTimestamp.millis);
|
|
263
|
+
}
|
|
264
|
+
if (lastTimestamp == null || lastTimestamp !== message.timestamp) {
|
|
265
|
+
const { changes } = yield* sqlite.exec({
|
|
266
|
+
...Sql.insertIntoMessagesIfNew,
|
|
267
|
+
parameters: [
|
|
268
|
+
message.timestamp,
|
|
269
|
+
message.table,
|
|
270
|
+
message.row,
|
|
271
|
+
message.column,
|
|
272
|
+
message.value,
|
|
273
|
+
],
|
|
274
|
+
});
|
|
275
|
+
if (changes === 1)
|
|
276
|
+
merkleTree = insertIntoMerkleTree(merkleTree, messageTimestamp);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return merkleTree;
|
|
280
|
+
});
|
|
281
|
+
export const upsertValueIntoTableRowColumn = (message, messages, millis) => Sqlite.pipe(Effect.map((sqlite) => {
|
|
282
|
+
const now = cast(new Date(millis));
|
|
283
|
+
return sqlite.exec({
|
|
284
|
+
sql: `
|
|
285
|
+
insert into
|
|
286
|
+
"${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
|
|
287
|
+
values
|
|
288
|
+
(?, ?, ?, ?)
|
|
289
|
+
on conflict do update set
|
|
290
|
+
"${message.column}" = ?,
|
|
291
|
+
"updatedAt" = ?
|
|
292
|
+
`.trim(),
|
|
293
|
+
parameters: [message.row, message.value, now, now, message.value, now],
|
|
294
|
+
});
|
|
295
|
+
}), Effect.flatMap((insert) => Effect.catchSomeDefect(insert, (error) => S.is(SqliteNoSuchTableOrColumnError)(error)
|
|
296
|
+
? Option.some(
|
|
297
|
+
// If one message fails, we ensure schema for all messages.
|
|
298
|
+
ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)))
|
|
299
|
+
: Option.none())));
|
|
300
|
+
const SqliteNoSuchTableOrColumnError = S.Struct({
|
|
301
|
+
message: S.Union(S.String.pipe(S.includes("no such table")), S.String.pipe(S.includes("no such column")), S.String.pipe(S.includes("has no column"))),
|
|
302
|
+
});
|
|
303
|
+
const ensureSchemaByNewMessages = (messages) => Effect.gen(function* () {
|
|
304
|
+
const tablesMap = new Map();
|
|
305
|
+
messages.forEach((message) => {
|
|
306
|
+
const table = tablesMap.get(message.table);
|
|
307
|
+
if (table == null) {
|
|
308
|
+
tablesMap.set(message.table, {
|
|
309
|
+
name: message.table,
|
|
310
|
+
columns: [message.column, "createdAt", "updatedAt"],
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (table.columns.includes(message.column))
|
|
315
|
+
return;
|
|
316
|
+
tablesMap.set(message.table, {
|
|
317
|
+
name: message.table,
|
|
318
|
+
columns: table.columns.concat(message.column),
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
const tables = Arr.fromIterable(tablesMap.values());
|
|
322
|
+
yield* Effect.flatMap(getSchema, ensureSchema({ tables, indexes: [] }));
|
|
323
|
+
});
|
|
324
|
+
const writeTimestampAndMerkleTree = (timestamp, merkleTree) => Effect.flatMap(Sqlite, (sqlite) => sqlite.exec({
|
|
325
|
+
...Sql.updateOwnerTimestampAndMerkleTree,
|
|
326
|
+
parameters: [
|
|
327
|
+
merkleTreeToString(merkleTree),
|
|
328
|
+
timestampToString(timestamp),
|
|
329
|
+
],
|
|
330
|
+
}));
|
|
331
|
+
const dropAllTables = Effect.gen(function* () {
|
|
332
|
+
yield* Effect.logTrace("Db dropAllTables");
|
|
333
|
+
const sqlite = yield* Sqlite;
|
|
334
|
+
const schema = yield* getSchema;
|
|
335
|
+
yield* Effect.forEach(schema.tables, (table) =>
|
|
336
|
+
// The dropped table is completely removed from the database schema and
|
|
337
|
+
// the disk file. The table can not be recovered.
|
|
338
|
+
// All indices and triggers associated with the table are also deleted.
|
|
339
|
+
// https://sqlite.org/lang_droptable.html
|
|
340
|
+
sqlite.exec({ sql: `drop table "${table.name}"` }));
|
|
341
|
+
});
|
|
342
|
+
const forkSync = (messages = []) => SyncLock.pipe(Effect.flatMap((syncLock) => syncLock.tryAcquire), Effect.matchEffect({
|
|
343
|
+
onFailure: () => Effect.void,
|
|
344
|
+
onSuccess: () => syncLoop(messages),
|
|
345
|
+
}), Effect.scoped, Effect.forkDaemon);
|
|
346
|
+
const syncLoop = (messages = []) => Effect.gen(function* (_) {
|
|
347
|
+
const sqlite = yield* Sqlite;
|
|
348
|
+
const sync = yield* Sync;
|
|
349
|
+
const callbacks = yield* Callbacks;
|
|
350
|
+
const time = yield* Time;
|
|
351
|
+
const syncDataRef = yield* readTimestampAndMerkleTree.pipe(sqlite.transaction("shared"), Effect.map((a) => ({ ...a, messages })), Effect.flatMap(Ref.make));
|
|
352
|
+
callbacks.onSyncStateChange({ _tag: "SyncStateIsSyncing" });
|
|
353
|
+
yield* Ref.get(syncDataRef).pipe(Effect.flatMap(sync.sync), Effect.flatMap(handleSyncResult), Effect.repeat({
|
|
354
|
+
// TODO: Schedule.jittered(Schedule.exponential("10 millis"))
|
|
355
|
+
schedule: Schedule.recurs(50),
|
|
356
|
+
until: Option.match({
|
|
357
|
+
onNone: () => Effect.gen(function* () {
|
|
358
|
+
callbacks.onSyncStateChange({
|
|
359
|
+
_tag: "SyncStateIsSynced",
|
|
360
|
+
time: yield* time.now,
|
|
361
|
+
});
|
|
362
|
+
return true;
|
|
363
|
+
}),
|
|
364
|
+
onSome: (syncData) => Effect.gen(function* () {
|
|
365
|
+
yield* Ref.set(syncDataRef, syncData);
|
|
366
|
+
return false;
|
|
367
|
+
}),
|
|
368
|
+
}),
|
|
369
|
+
}), Effect.catchAllDefect((error) =>
|
|
370
|
+
// Db can run in a Web Worker, so we must ensure transferable error.
|
|
371
|
+
Effect.fail(makeUnexpectedError(ensureTransferableError(error)))), Effect.catchTag("SyncStateIsNotSynced", (state) => Effect.succeed(callbacks.onSyncStateChange(state))), Effect.catchAll((error) => Effect.succeed(callbacks.onError(error))));
|
|
372
|
+
});
|
|
373
|
+
const handleSyncResult = (result) => Effect.flatMap(Sqlite, (sqlite) => Effect.gen(function* (_) {
|
|
374
|
+
const { onReceive } = yield* Callbacks;
|
|
375
|
+
const current = yield* readTimestampAndMerkleTree;
|
|
376
|
+
const nextTimestamp = yield* Effect.reduce(result.messages, current.timestamp, (local, message) => receiveTimestamp({
|
|
377
|
+
local,
|
|
378
|
+
remote: unsafeTimestampFromString(message.timestamp),
|
|
379
|
+
}));
|
|
380
|
+
const nextMerkleTree = yield* applyMessages(current.merkleTree, result.messages);
|
|
381
|
+
if (result.messages.length > 0) {
|
|
382
|
+
yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
|
|
383
|
+
onReceive();
|
|
384
|
+
}
|
|
385
|
+
return yield* diffMerkleTrees(result.merkleTree, nextMerkleTree).pipe(Effect.flatMap((diff) => sqlite.exec({
|
|
386
|
+
...Sql.selectMessagesToSync,
|
|
387
|
+
parameters: [timestampToString(makeSyncTimestamp(diff))],
|
|
388
|
+
})), Effect.map(({ rows }) => ({
|
|
389
|
+
messages: rows,
|
|
390
|
+
merkleTree: nextMerkleTree,
|
|
391
|
+
timestamp: nextTimestamp,
|
|
392
|
+
})), Effect.option);
|
|
393
|
+
}).pipe(sqlite.transaction("exclusive")));
|
|
394
|
+
// We use queries as keys, hence JSON.stringify.
|
|
395
|
+
export const serializeQuery = ({ sql, parameters = [], options, }) => {
|
|
396
|
+
const query = {
|
|
397
|
+
sql,
|
|
398
|
+
parameters: parameters.map((p) => Predicate.isUint8Array(p)
|
|
399
|
+
? Arr.fromIterable(p)
|
|
400
|
+
: isJsonObjectOrArray(p)
|
|
401
|
+
? { json: p }
|
|
402
|
+
: p),
|
|
403
|
+
...(options && { options }),
|
|
404
|
+
};
|
|
405
|
+
return JSON.stringify(query);
|
|
406
|
+
};
|
|
407
|
+
export const deserializeQuery = (query) => {
|
|
408
|
+
const serializedSqliteQuery = JSON.parse(query);
|
|
409
|
+
return {
|
|
410
|
+
...serializedSqliteQuery,
|
|
411
|
+
parameters: serializedSqliteQuery.parameters.map((p) => Arr.isArray(p)
|
|
412
|
+
? new Uint8Array(p)
|
|
413
|
+
: typeof p === "object" && p != null
|
|
414
|
+
? p.json
|
|
415
|
+
: p),
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
// To preserve identity.
|
|
419
|
+
const _emptyRows = [];
|
|
420
|
+
export const emptyRows = () => _emptyRows;
|
|
421
|
+
// To preserve identity.
|
|
422
|
+
const queryResultCache = new WeakMap();
|
|
423
|
+
export const queryResultFromRows = (rows) => {
|
|
424
|
+
let queryResult = queryResultCache.get(rows);
|
|
425
|
+
if (queryResult == null) {
|
|
426
|
+
queryResult = { rows, row: rows[0] };
|
|
427
|
+
queryResultCache.set(rows, queryResult);
|
|
428
|
+
}
|
|
429
|
+
return queryResult;
|
|
430
|
+
};
|
|
431
|
+
export const notSupportedPlatformWorker = {
|
|
432
|
+
init: () => Effect.fail({
|
|
433
|
+
_tag: "NotSupportedPlatformError",
|
|
434
|
+
}),
|
|
435
|
+
loadQueries: () => Effect.succeed([]),
|
|
436
|
+
mutate: () => Effect.succeed([]),
|
|
437
|
+
resetOwner: () => Effect.void,
|
|
438
|
+
restoreOwner: () => Effect.void,
|
|
439
|
+
ensureSchema: () => Effect.void,
|
|
440
|
+
sync: () => Effect.succeed([]),
|
|
441
|
+
};
|