@evolu/common 5.4.8 → 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/dist/src/Db.js
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
import * as S from "@effect/schema/Schema";
|
|
2
|
-
import * as Arr from "effect/Array";
|
|
3
|
-
import * as Console from "effect/Console";
|
|
4
|
-
import * as Context from "effect/Context";
|
|
5
|
-
import * as Deferred from "effect/Deferred";
|
|
6
|
-
import * as Effect from "effect/Effect";
|
|
7
|
-
import { constVoid, pipe } from "effect/Function";
|
|
8
|
-
import * as Option from "effect/Option";
|
|
9
|
-
import * as Predicate from "effect/Predicate";
|
|
10
|
-
import * as Ref from "effect/Ref";
|
|
11
|
-
import * as Schedule from "effect/Schedule";
|
|
12
|
-
import * as String from "effect/String";
|
|
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 { 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,
|
|
27
|
-
});
|
|
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(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, mnemonic) => 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(mnemonic).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
|
-
exportDatabase: () => Effect.logTrace("Db exportDatabase").pipe(Effect.zipRight(Sqlite), Effect.flatMap((sqlite) => sqlite.export()), afterInit({ transaction: "exclusive" })),
|
|
86
|
-
// TODO:
|
|
87
|
-
// dispose: () =>
|
|
88
|
-
// Effect.logTrace("Db dispose").pipe(
|
|
89
|
-
// Effect.tap(Scope.close(scope, Exit.succeed("Db disposed"))),
|
|
90
|
-
// afterInit({ transaction: "last" }),
|
|
91
|
-
// ),
|
|
92
|
-
};
|
|
93
|
-
return db;
|
|
94
|
-
});
|
|
95
|
-
export const Callbacks = Context.GenericTag("Callbacks");
|
|
96
|
-
const loadQueries = (queries, queryRowsRef) => Effect.gen(function* () {
|
|
97
|
-
const sqlite = yield* Sqlite;
|
|
98
|
-
const previousState = yield* SynchronizedRef.getAndUpdateEffect(queryRowsRef, (previousState) => Effect.map(Effect.forEach(queries, (query) => {
|
|
99
|
-
const sqliteQuery = deserializeQuery(query);
|
|
100
|
-
return sqlite.exec(sqliteQuery).pipe(Effect.tap(maybeExplainQueryPlan(sqliteQuery)), Effect.map(({ rows }) => [query, rows]));
|
|
101
|
-
}), (queriesRows) => new Map([...previousState, ...queriesRows])));
|
|
102
|
-
const currentState = yield* SynchronizedRef.get(queryRowsRef);
|
|
103
|
-
return queries.map((query) => ({
|
|
104
|
-
query,
|
|
105
|
-
patches: makePatches(previousState.get(query), currentState.get(query) || []),
|
|
106
|
-
}));
|
|
107
|
-
});
|
|
108
|
-
const maybeExplainQueryPlan = (sqliteQuery) => {
|
|
109
|
-
if (!sqliteQuery.options?.logExplainQueryPlan)
|
|
110
|
-
return Effect.void;
|
|
111
|
-
return Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec({
|
|
112
|
-
...sqliteQuery,
|
|
113
|
-
sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
|
|
114
|
-
})), Effect.tap(Console.log("ExplainQueryPlan", sqliteQuery)), Effect.tap(({ rows }) => Console.log(drawSqliteQueryPlan(rows))), Effect.map(constVoid));
|
|
115
|
-
};
|
|
116
|
-
const getSchema = Effect.gen(function* (_) {
|
|
117
|
-
yield* Effect.logTrace("Db getSchema");
|
|
118
|
-
const sqlite = yield* Sqlite;
|
|
119
|
-
const tables = yield* Effect.map(sqlite.exec({
|
|
120
|
-
// https://til.simonwillison.net/sqlite/list-all-columns-in-a-database
|
|
121
|
-
sql: `
|
|
122
|
-
select
|
|
123
|
-
sqlite_master.name as tableName,
|
|
124
|
-
table_info.name as columnName
|
|
125
|
-
from
|
|
126
|
-
sqlite_master
|
|
127
|
-
join pragma_table_info(sqlite_master.name) as table_info
|
|
128
|
-
`.trim(),
|
|
129
|
-
}), ({ rows }) => {
|
|
130
|
-
const map = new Map();
|
|
131
|
-
rows.forEach((row) => {
|
|
132
|
-
const { tableName, columnName } = row;
|
|
133
|
-
if (!map.has(tableName))
|
|
134
|
-
map.set(tableName, []);
|
|
135
|
-
map.get(tableName)?.push(columnName);
|
|
136
|
-
});
|
|
137
|
-
return globalThis.Array.from(map, ([name, columns]) => ({
|
|
138
|
-
name,
|
|
139
|
-
columns,
|
|
140
|
-
}));
|
|
141
|
-
});
|
|
142
|
-
const indexes = yield* Effect.map(sqlite.exec({
|
|
143
|
-
sql: `
|
|
144
|
-
select
|
|
145
|
-
name, sql
|
|
146
|
-
from
|
|
147
|
-
sqlite_master
|
|
148
|
-
where
|
|
149
|
-
type='index' and
|
|
150
|
-
name not like 'sqlite_%' and
|
|
151
|
-
name not like 'index_evolu_%'
|
|
152
|
-
`.trim(),
|
|
153
|
-
}), (result) => Arr.map(result.rows, (row) => ({
|
|
154
|
-
name: row.name,
|
|
155
|
-
/**
|
|
156
|
-
* SQLite returns "CREATE INDEX" for "create index" for some reason.
|
|
157
|
-
* Other keywords remain unchanged. We have to normalize the casing
|
|
158
|
-
* for `indexEquivalence` manually.
|
|
159
|
-
*/
|
|
160
|
-
sql: row.sql.replace("CREATE INDEX", "create index"),
|
|
161
|
-
})));
|
|
162
|
-
return { tables, indexes };
|
|
163
|
-
});
|
|
164
|
-
const ensureSchema = (newSchema) => (currentSchema) => Effect.gen(function* () {
|
|
165
|
-
yield* Effect.logTrace("Db ensureSchema");
|
|
166
|
-
const sql = [];
|
|
167
|
-
newSchema.tables.forEach((table) => {
|
|
168
|
-
const currentTable = currentSchema.tables.find((t) => t.name === table.name);
|
|
169
|
-
if (!currentTable) {
|
|
170
|
-
sql.push(`
|
|
171
|
-
create table ${table.name} (
|
|
172
|
-
"id" text primary key,
|
|
173
|
-
${table.columns
|
|
174
|
-
.filter((c) => c !== "id")
|
|
175
|
-
// "A column with affinity BLOB does not prefer one storage class over another
|
|
176
|
-
// and no attempt is made to coerce data from one storage class into another."
|
|
177
|
-
// https://www.sqlite.org/datatype3.html
|
|
178
|
-
.map((name) => `"${name}" blob`)
|
|
179
|
-
.join(", ")}
|
|
180
|
-
);`.trim());
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
Arr.differenceWith(String.Equivalence)(table.columns, currentTable.columns).forEach((newColumn) => {
|
|
184
|
-
sql.push(`alter table "${table.name}" add column "${newColumn}" blob;`);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
// Remove old indexes.
|
|
189
|
-
Arr.differenceWith(indexEquivalence)(currentSchema.indexes, Arr.intersectionWith(indexEquivalence)(currentSchema.indexes, newSchema.indexes)).forEach((indexToDrop) => {
|
|
190
|
-
sql.push(`drop index "${indexToDrop.name}";`);
|
|
191
|
-
});
|
|
192
|
-
// Add new indexes.
|
|
193
|
-
Arr.differenceWith(indexEquivalence)(newSchema.indexes, currentSchema.indexes).forEach((newIndex) => {
|
|
194
|
-
sql.push(`${newIndex.sql};`);
|
|
195
|
-
});
|
|
196
|
-
if (sql.length > 0) {
|
|
197
|
-
const sqlite = yield* Sqlite;
|
|
198
|
-
yield* sqlite.exec({ sql: sql.join("\n") });
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
const indexEquivalence = (self, that) => self.name === that.name && self.sql === that.sql;
|
|
202
|
-
const readOwner = Effect.logTrace("Db readOwner").pipe(Effect.zipRight(Sqlite), Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwner)), Effect.map(({ rows: [row] }) => ({
|
|
203
|
-
id: row.id,
|
|
204
|
-
mnemonic: row.mnemonic,
|
|
205
|
-
encryptionKey: row.encryptionKey,
|
|
206
|
-
})));
|
|
207
|
-
const createOwner = (mnemonic) => Effect.logTrace("Db createOwner").pipe(Effect.zipRight(Effect.all([makeOwner(mnemonic), Sqlite, makeInitialTimestamp])), Effect.tap(([owner, sqlite, initialTimestampString]) => Effect.all([
|
|
208
|
-
sqlite.exec(Sql.createMessageTable),
|
|
209
|
-
sqlite.exec(Sql.createMessageTableIndex),
|
|
210
|
-
sqlite.exec(Sql.createOwnerTable),
|
|
211
|
-
sqlite.exec({
|
|
212
|
-
...Sql.insertOwner,
|
|
213
|
-
parameters: [
|
|
214
|
-
owner.id,
|
|
215
|
-
owner.mnemonic,
|
|
216
|
-
owner.encryptionKey,
|
|
217
|
-
timestampToString(initialTimestampString),
|
|
218
|
-
merkleTreeToString(initialMerkleTree),
|
|
219
|
-
],
|
|
220
|
-
}),
|
|
221
|
-
])), Effect.map(([owner]) => owner));
|
|
222
|
-
const applyMutations = (mutations) => Effect.gen(function* (_) {
|
|
223
|
-
const { timestamp, merkleTree } = yield* readTimestampAndMerkleTree;
|
|
224
|
-
const [nextTimestamp, messages] = yield* Effect.mapAccum(mutations.flatMap(mutationToNewMessages), timestamp, (currentTimestamp, newMessage) => Effect.map(sendTimestamp(currentTimestamp), (nextTimestamp) => {
|
|
225
|
-
const message = {
|
|
226
|
-
...newMessage,
|
|
227
|
-
timestamp: timestampToString(nextTimestamp),
|
|
228
|
-
};
|
|
229
|
-
return [nextTimestamp, message];
|
|
230
|
-
}));
|
|
231
|
-
const nextMerkleTree = yield* applyMessages(merkleTree, messages);
|
|
232
|
-
yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
|
|
233
|
-
return messages;
|
|
234
|
-
});
|
|
235
|
-
const readTimestampAndMerkleTree = Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree)), Effect.map(({ rows: [{ timestamp, merkleTree }] }) => ({
|
|
236
|
-
timestamp: unsafeTimestampFromString(timestamp),
|
|
237
|
-
merkleTree: merkleTree,
|
|
238
|
-
})));
|
|
239
|
-
const mutationToNewMessages = (mutation) => pipe(Object.entries(mutation.values), Arr.filterMap(([column, value]) =>
|
|
240
|
-
// The value can be undefined if exactOptionalPropertyTypes isn't true.
|
|
241
|
-
// Don't insert nulls because null is the default value.
|
|
242
|
-
value === undefined || (mutation.isInsert && value == null)
|
|
243
|
-
? Option.none()
|
|
244
|
-
: Option.some([column, value])), Arr.map(([column, value]) => ({
|
|
245
|
-
table: mutation.table,
|
|
246
|
-
row: mutation.id,
|
|
247
|
-
column,
|
|
248
|
-
value: typeof value === "boolean"
|
|
249
|
-
? cast(value)
|
|
250
|
-
: value instanceof Date
|
|
251
|
-
? cast(value)
|
|
252
|
-
: value,
|
|
253
|
-
})));
|
|
254
|
-
const applyMessages = (merkleTree, messages) => Effect.gen(function* () {
|
|
255
|
-
const sqlite = yield* Sqlite;
|
|
256
|
-
for (const message of messages) {
|
|
257
|
-
const messageTimestamp = unsafeTimestampFromString(message.timestamp);
|
|
258
|
-
const lastTimestamp = yield* Effect.map(sqlite.exec({
|
|
259
|
-
...Sql.selectLastTimestampForTableRowColumn,
|
|
260
|
-
parameters: [message.table, message.row, message.column, 1],
|
|
261
|
-
}), ({ rows }) => rows.length > 0 ? rows[0].timestamp : null);
|
|
262
|
-
if (lastTimestamp == null || lastTimestamp < message.timestamp) {
|
|
263
|
-
yield* upsertValueIntoTableRowColumn(message, messages, messageTimestamp.millis);
|
|
264
|
-
}
|
|
265
|
-
if (lastTimestamp == null || lastTimestamp !== message.timestamp) {
|
|
266
|
-
const { changes } = yield* sqlite.exec({
|
|
267
|
-
...Sql.insertIntoMessagesIfNew,
|
|
268
|
-
parameters: [
|
|
269
|
-
message.timestamp,
|
|
270
|
-
message.table,
|
|
271
|
-
message.row,
|
|
272
|
-
message.column,
|
|
273
|
-
message.value,
|
|
274
|
-
],
|
|
275
|
-
});
|
|
276
|
-
if (changes === 1)
|
|
277
|
-
merkleTree = insertIntoMerkleTree(merkleTree, messageTimestamp);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return merkleTree;
|
|
281
|
-
});
|
|
282
|
-
export const upsertValueIntoTableRowColumn = (message, messages, millis) => Sqlite.pipe(Effect.map((sqlite) => {
|
|
283
|
-
const now = cast(new Date(millis));
|
|
284
|
-
return sqlite.exec({
|
|
285
|
-
sql: `
|
|
286
|
-
insert into
|
|
287
|
-
"${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
|
|
288
|
-
values
|
|
289
|
-
(?, ?, ?, ?)
|
|
290
|
-
on conflict do update set
|
|
291
|
-
"${message.column}" = ?,
|
|
292
|
-
"updatedAt" = ?
|
|
293
|
-
`.trim(),
|
|
294
|
-
parameters: [message.row, message.value, now, now, message.value, now],
|
|
295
|
-
});
|
|
296
|
-
}), Effect.flatMap((insert) => Effect.catchSomeDefect(insert, (error) => S.is(SqliteNoSuchTableOrColumnError)(error)
|
|
297
|
-
? Option.some(
|
|
298
|
-
// If one message fails, we ensure schema for all messages.
|
|
299
|
-
ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)))
|
|
300
|
-
: Option.none())));
|
|
301
|
-
const SqliteNoSuchTableOrColumnError = S.Struct({
|
|
302
|
-
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"))),
|
|
303
|
-
});
|
|
304
|
-
const ensureSchemaByNewMessages = (messages) => Effect.gen(function* () {
|
|
305
|
-
const tablesMap = new Map();
|
|
306
|
-
messages.forEach((message) => {
|
|
307
|
-
const table = tablesMap.get(message.table);
|
|
308
|
-
if (table == null) {
|
|
309
|
-
tablesMap.set(message.table, {
|
|
310
|
-
name: message.table,
|
|
311
|
-
columns: [message.column, "createdAt", "updatedAt"],
|
|
312
|
-
});
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (table.columns.includes(message.column))
|
|
316
|
-
return;
|
|
317
|
-
tablesMap.set(message.table, {
|
|
318
|
-
name: message.table,
|
|
319
|
-
columns: table.columns.concat(message.column),
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
const tables = Arr.fromIterable(tablesMap.values());
|
|
323
|
-
yield* Effect.flatMap(getSchema, ensureSchema({ tables, indexes: [] }));
|
|
324
|
-
});
|
|
325
|
-
const writeTimestampAndMerkleTree = (timestamp, merkleTree) => Effect.flatMap(Sqlite, (sqlite) => sqlite.exec({
|
|
326
|
-
...Sql.updateOwnerTimestampAndMerkleTree,
|
|
327
|
-
parameters: [
|
|
328
|
-
merkleTreeToString(merkleTree),
|
|
329
|
-
timestampToString(timestamp),
|
|
330
|
-
],
|
|
331
|
-
}));
|
|
332
|
-
const dropAllTables = Effect.gen(function* () {
|
|
333
|
-
yield* Effect.logTrace("Db dropAllTables");
|
|
334
|
-
const sqlite = yield* Sqlite;
|
|
335
|
-
const schema = yield* getSchema;
|
|
336
|
-
yield* Effect.forEach(schema.tables, (table) =>
|
|
337
|
-
// The dropped table is completely removed from the database schema and
|
|
338
|
-
// the disk file. The table can not be recovered.
|
|
339
|
-
// All indices and triggers associated with the table are also deleted.
|
|
340
|
-
// https://sqlite.org/lang_droptable.html
|
|
341
|
-
sqlite.exec({ sql: `drop table "${table.name}"` }));
|
|
342
|
-
});
|
|
343
|
-
const forkSync = (messages = []) => SyncLock.pipe(Effect.flatMap((syncLock) => syncLock.tryAcquire), Effect.matchEffect({
|
|
344
|
-
onFailure: () => Effect.void,
|
|
345
|
-
onSuccess: () => syncLoop(messages),
|
|
346
|
-
}), Effect.scoped, Effect.forkDaemon);
|
|
347
|
-
const syncLoop = (messages = []) => Effect.gen(function* (_) {
|
|
348
|
-
const sqlite = yield* Sqlite;
|
|
349
|
-
const sync = yield* Sync;
|
|
350
|
-
const callbacks = yield* Callbacks;
|
|
351
|
-
const time = yield* Time;
|
|
352
|
-
const syncDataRef = yield* readTimestampAndMerkleTree.pipe(sqlite.transaction("shared"), Effect.map((a) => ({ ...a, messages })), Effect.flatMap(Ref.make));
|
|
353
|
-
callbacks.onSyncStateChange({ _tag: "SyncStateIsSyncing" });
|
|
354
|
-
yield* Ref.get(syncDataRef).pipe(Effect.flatMap(sync.sync), Effect.flatMap(handleSyncResult), Effect.repeat({
|
|
355
|
-
// TODO: Schedule.jittered(Schedule.exponential("10 millis"))
|
|
356
|
-
schedule: Schedule.recurs(50),
|
|
357
|
-
until: Option.match({
|
|
358
|
-
onNone: () => Effect.gen(function* () {
|
|
359
|
-
callbacks.onSyncStateChange({
|
|
360
|
-
_tag: "SyncStateIsSynced",
|
|
361
|
-
time: yield* time.now,
|
|
362
|
-
});
|
|
363
|
-
return true;
|
|
364
|
-
}),
|
|
365
|
-
onSome: (syncData) => Effect.gen(function* () {
|
|
366
|
-
yield* Ref.set(syncDataRef, syncData);
|
|
367
|
-
return false;
|
|
368
|
-
}),
|
|
369
|
-
}),
|
|
370
|
-
}), Effect.catchAllDefect((error) =>
|
|
371
|
-
// Db can run in a Web Worker, so we must ensure transferable error.
|
|
372
|
-
Effect.fail(makeUnexpectedError(ensureTransferableError(error)))), Effect.catchTag("SyncStateIsNotSynced", (state) => Effect.succeed(callbacks.onSyncStateChange(state))), Effect.catchAll((error) => Effect.succeed(callbacks.onError(error))));
|
|
373
|
-
});
|
|
374
|
-
const handleSyncResult = (result) => Effect.flatMap(Sqlite, (sqlite) => Effect.gen(function* (_) {
|
|
375
|
-
const { onReceive } = yield* Callbacks;
|
|
376
|
-
const current = yield* readTimestampAndMerkleTree;
|
|
377
|
-
const nextTimestamp = yield* Effect.reduce(result.messages, current.timestamp, (local, message) => receiveTimestamp({
|
|
378
|
-
local,
|
|
379
|
-
remote: unsafeTimestampFromString(message.timestamp),
|
|
380
|
-
}));
|
|
381
|
-
const nextMerkleTree = yield* applyMessages(current.merkleTree, result.messages);
|
|
382
|
-
if (result.messages.length > 0) {
|
|
383
|
-
yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
|
|
384
|
-
onReceive();
|
|
385
|
-
}
|
|
386
|
-
return yield* diffMerkleTrees(result.merkleTree, nextMerkleTree).pipe(Effect.flatMap((diff) => sqlite.exec({
|
|
387
|
-
...Sql.selectMessagesToSync,
|
|
388
|
-
parameters: [timestampToString(makeSyncTimestamp(diff))],
|
|
389
|
-
})), Effect.map(({ rows }) => ({
|
|
390
|
-
messages: rows,
|
|
391
|
-
merkleTree: nextMerkleTree,
|
|
392
|
-
timestamp: nextTimestamp,
|
|
393
|
-
})), Effect.option);
|
|
394
|
-
}).pipe(sqlite.transaction("exclusive")));
|
|
395
|
-
// We use queries as keys, hence JSON.stringify.
|
|
396
|
-
export const serializeQuery = ({ sql, parameters = [], options, }) => {
|
|
397
|
-
const query = {
|
|
398
|
-
sql,
|
|
399
|
-
parameters: parameters.map((p) => Predicate.isUint8Array(p)
|
|
400
|
-
? Arr.fromIterable(p)
|
|
401
|
-
: isJsonObjectOrArray(p)
|
|
402
|
-
? { json: p }
|
|
403
|
-
: p),
|
|
404
|
-
...(options && { options }),
|
|
405
|
-
};
|
|
406
|
-
return JSON.stringify(query);
|
|
407
|
-
};
|
|
408
|
-
export const deserializeQuery = (query) => {
|
|
409
|
-
const serializedSqliteQuery = JSON.parse(query);
|
|
410
|
-
return {
|
|
411
|
-
...serializedSqliteQuery,
|
|
412
|
-
parameters: serializedSqliteQuery.parameters.map((p) => Arr.isArray(p)
|
|
413
|
-
? new Uint8Array(p)
|
|
414
|
-
: typeof p === "object" && p != null
|
|
415
|
-
? p.json
|
|
416
|
-
: p),
|
|
417
|
-
};
|
|
418
|
-
};
|
|
419
|
-
// To preserve identity.
|
|
420
|
-
const _emptyRows = [];
|
|
421
|
-
export const emptyRows = () => _emptyRows;
|
|
422
|
-
// To preserve identity.
|
|
423
|
-
const queryResultCache = new WeakMap();
|
|
424
|
-
export const queryResultFromRows = (rows) => {
|
|
425
|
-
let queryResult = queryResultCache.get(rows);
|
|
426
|
-
if (queryResult == null) {
|
|
427
|
-
queryResult = { rows, row: rows[0] };
|
|
428
|
-
queryResultCache.set(rows, queryResult);
|
|
429
|
-
}
|
|
430
|
-
return queryResult;
|
|
431
|
-
};
|
|
432
|
-
export const notSupportedPlatformWorker = {
|
|
433
|
-
init: () => Effect.fail({
|
|
434
|
-
_tag: "NotSupportedPlatformError",
|
|
435
|
-
}),
|
|
436
|
-
loadQueries: () => Effect.succeed([]),
|
|
437
|
-
mutate: () => Effect.succeed([]),
|
|
438
|
-
resetOwner: () => Effect.void,
|
|
439
|
-
restoreOwner: () => Effect.void,
|
|
440
|
-
ensureSchema: () => Effect.void,
|
|
441
|
-
sync: () => Effect.succeed([]),
|
|
442
|
-
exportDatabase: () => Effect.succeed(new Uint8Array()),
|
|
443
|
-
};
|
package/dist/src/Diff.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Query, Row } from "./Db.js";
|
|
2
|
-
import { Value } from "./Sqlite.js";
|
|
3
|
-
export interface QueryPatches {
|
|
4
|
-
readonly query: Query;
|
|
5
|
-
readonly patches: ReadonlyArray<Patch>;
|
|
6
|
-
}
|
|
7
|
-
export type Patch = ReplaceAllPatch | ReplaceAtPatch;
|
|
8
|
-
export interface ReplaceAllPatch {
|
|
9
|
-
readonly op: "replaceAll";
|
|
10
|
-
readonly value: ReadonlyArray<Row>;
|
|
11
|
-
}
|
|
12
|
-
export interface ReplaceAtPatch {
|
|
13
|
-
readonly op: "replaceAt";
|
|
14
|
-
readonly index: number;
|
|
15
|
-
readonly value: Row;
|
|
16
|
-
}
|
|
17
|
-
export declare const applyPatches: (patches: ReadonlyArray<Patch>, current: ReadonlyArray<Row>) => ReadonlyArray<Row>;
|
|
18
|
-
/**
|
|
19
|
-
* We detect only changes in the whole result and in-place edits. In the future,
|
|
20
|
-
* we will add more heuristics. We will probably not implement the Myers diff
|
|
21
|
-
* algorithm because it's faster to rerender all than to compute many detailed
|
|
22
|
-
* patches. We will only implement logic a developer would implement manually,
|
|
23
|
-
* if necessary.
|
|
24
|
-
*/
|
|
25
|
-
export declare const makePatches: (previousRows: ReadonlyArray<Row> | undefined, nextRows: ReadonlyArray<Row>) => readonly Patch[];
|
|
26
|
-
export declare const areEqual: (a: Value | Row | ReadonlyArray<Row>, b: Value | Row | ReadonlyArray<Row>) => boolean;
|
|
27
|
-
//# sourceMappingURL=Diff.d.ts.map
|
package/dist/src/Diff.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Diff.d.ts","sourceRoot":"","sources":["../../src/Diff.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,KAAK,GAAG,eAAe,GAAG,cAAc,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;CACrB;AAED,eAAO,MAAM,YAAY,YACd,aAAa,CAAC,KAAK,CAAC,WACpB,aAAa,CAAC,GAAG,CAAC,KAC1B,aAAa,CAAC,GAAG,CASP,CAAC;AAEd;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,iBACR,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS,YAClC,aAAa,CAAC,GAAG,CAAC,KAC3B,SAAS,KAAK,EA2BhB,CAAC;AAEF,eAAO,MAAM,QAAQ,MAChB,KAAK,GAAG,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,KAChC,KAAK,GAAG,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,KAClC,OAkCF,CAAC"}
|
package/dist/src/Diff.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import * as Arr from "effect/Array";
|
|
2
|
-
import * as Predicate from "effect/Predicate";
|
|
3
|
-
export const applyPatches = (patches, current) => patches.reduce((next, patch) => {
|
|
4
|
-
switch (patch.op) {
|
|
5
|
-
case "replaceAll":
|
|
6
|
-
return patch.value;
|
|
7
|
-
case "replaceAt": {
|
|
8
|
-
return Arr.replace(next, patch.index, patch.value);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}, current);
|
|
12
|
-
/**
|
|
13
|
-
* We detect only changes in the whole result and in-place edits. In the future,
|
|
14
|
-
* we will add more heuristics. We will probably not implement the Myers diff
|
|
15
|
-
* algorithm because it's faster to rerender all than to compute many detailed
|
|
16
|
-
* patches. We will only implement logic a developer would implement manually,
|
|
17
|
-
* if necessary.
|
|
18
|
-
*/
|
|
19
|
-
export const makePatches = (previousRows, nextRows) => {
|
|
20
|
-
if (previousRows === undefined)
|
|
21
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
22
|
-
// TODO: Detect prepend and append, it's cheap.
|
|
23
|
-
if (previousRows.length !== nextRows.length) {
|
|
24
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
25
|
-
}
|
|
26
|
-
const length = previousRows.length;
|
|
27
|
-
const replaceAtPatches = [];
|
|
28
|
-
for (let i = 0; i < length; i++) {
|
|
29
|
-
const previousRow = previousRows[i];
|
|
30
|
-
const nextRow = nextRows[i];
|
|
31
|
-
// We expect the same shape for both rows.
|
|
32
|
-
for (const key in previousRow)
|
|
33
|
-
if (!areEqual(previousRow[key], nextRow[key])) {
|
|
34
|
-
replaceAtPatches.push({ op: "replaceAt", value: nextRow, index: i });
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (length > 0 && replaceAtPatches.length === length) {
|
|
39
|
-
return [{ op: "replaceAll", value: nextRows }];
|
|
40
|
-
}
|
|
41
|
-
return replaceAtPatches;
|
|
42
|
-
};
|
|
43
|
-
export const areEqual = (a, b) => {
|
|
44
|
-
// Compare string, number, null ASAP.
|
|
45
|
-
if (a === b)
|
|
46
|
-
return true;
|
|
47
|
-
// Different type works only for string and number, everything else is an object.
|
|
48
|
-
if (typeof a !== typeof b)
|
|
49
|
-
return false;
|
|
50
|
-
// Both are nonnullable objects.
|
|
51
|
-
if (typeof a === "object" && a !== null && b !== null) {
|
|
52
|
-
const aIsUint8Array = Predicate.isUint8Array(a);
|
|
53
|
-
const bIsUint8Array = Predicate.isUint8Array(b);
|
|
54
|
-
if (aIsUint8Array && bIsUint8Array) {
|
|
55
|
-
if (a.length !== b.length)
|
|
56
|
-
return false;
|
|
57
|
-
for (let i = 0; i < a.length; i++)
|
|
58
|
-
if (a[i] !== b[i])
|
|
59
|
-
return false;
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
const aIsArray = Arr.isArray(a);
|
|
63
|
-
const bIsArray = Arr.isArray(b);
|
|
64
|
-
if (aIsArray && bIsArray) {
|
|
65
|
-
if (a.length !== b.length)
|
|
66
|
-
return false;
|
|
67
|
-
for (let i = 0; i < a.length; i++)
|
|
68
|
-
if (!areEqual(a[i], b[i]))
|
|
69
|
-
return false;
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
if (!aIsUint8Array && !bIsUint8Array && !aIsArray && !bIsArray) {
|
|
73
|
-
const aKeys = Object.keys(a);
|
|
74
|
-
const bKeys = Object.keys(b);
|
|
75
|
-
if (aKeys.length !== bKeys.length)
|
|
76
|
-
return false;
|
|
77
|
-
for (const key of aKeys)
|
|
78
|
-
if (!areEqual(a[key], b[key]))
|
|
79
|
-
return false;
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
};
|