@evolu/common 6.0.1-preview.15 → 6.0.1-preview.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Crypto.d.ts +0 -1
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +2 -1
- package/dist/src/Evolu/Config.d.ts +7 -7
- package/dist/src/Evolu/Config.d.ts.map +1 -1
- package/dist/src/Evolu/Db.d.ts +4 -8
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +136 -112
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +5 -4
- package/dist/src/Evolu/Owner.d.ts +84 -105
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +78 -89
- package/dist/src/Evolu/Protocol.d.ts +2 -2
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +1 -1
- package/dist/src/Evolu/Schema.d.ts +0 -7
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Crypto.ts +6 -4
- package/src/Evolu/Config.ts +7 -7
- package/src/Evolu/Db.ts +204 -159
- package/src/Evolu/Evolu.ts +6 -4
- package/src/Evolu/Owner.ts +128 -203
- package/src/Evolu/Protocol.ts +2 -8
- package/src/Evolu/Schema.ts +6 -7
package/src/Evolu/Config.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ConsoleConfig } from "../Console.js";
|
|
2
2
|
import { getOrThrow } from "../Result.js";
|
|
3
|
-
import {
|
|
3
|
+
import { SimpleName } from "../Type.js";
|
|
4
4
|
import type { DbIndexesBuilder } from "./Kysely.js";
|
|
5
|
+
import type { AppOwner } from "./Owner.js";
|
|
5
6
|
|
|
6
7
|
export interface Config extends ConsoleConfig {
|
|
7
8
|
/**
|
|
@@ -66,18 +67,17 @@ export interface Config extends ConsoleConfig {
|
|
|
66
67
|
readonly indexes?: DbIndexesBuilder;
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* until special UX requirements are needed (e.g., multitenancy).
|
|
70
|
+
* Initial AppOwner to use when creating Evolu instance. If omitted, a new
|
|
71
|
+
* AppOwner will be generated automatically.
|
|
72
72
|
*/
|
|
73
|
-
readonly
|
|
73
|
+
readonly initialAppOwner?: AppOwner;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Use in-memory SQLite database instead of persistent storage. Useful for
|
|
77
77
|
* testing or temporary data that doesn't need persistence.
|
|
78
78
|
*
|
|
79
|
-
* In-memory databases exist only in RAM and are completely destroyed when
|
|
80
|
-
*
|
|
79
|
+
* In-memory databases exist only in RAM and are completely destroyed when the
|
|
80
|
+
* process ends, making them forensically safe for sensitive data.
|
|
81
81
|
*
|
|
82
82
|
* The default value is: `false`.
|
|
83
83
|
*/
|
package/src/Evolu/Db.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
CreateMnemonicDep,
|
|
11
11
|
CreateRandomBytesDep,
|
|
12
12
|
createSymmetricCrypto,
|
|
13
|
+
EncryptionKey,
|
|
13
14
|
SymmetricCryptoDecryptError,
|
|
14
15
|
SymmetricCryptoDep,
|
|
15
16
|
} from "../Crypto.js";
|
|
@@ -19,7 +20,6 @@ import { constFalse, exhaustiveCheck } from "../Function.js";
|
|
|
19
20
|
import { NanoIdLibDep } from "../NanoId.js";
|
|
20
21
|
import { objectToEntries } from "../Object.js";
|
|
21
22
|
import { RandomDep } from "../Random.js";
|
|
22
|
-
import { createRef, Ref } from "../Ref.js";
|
|
23
23
|
import { ok, Result } from "../Result.js";
|
|
24
24
|
import {
|
|
25
25
|
createSqlite,
|
|
@@ -42,19 +42,14 @@ import {
|
|
|
42
42
|
} from "../Worker.js";
|
|
43
43
|
import { Config } from "./Config.js";
|
|
44
44
|
import { makePatches, QueryPatches } from "./Diff.js";
|
|
45
|
-
import {
|
|
46
|
-
AppOwner,
|
|
47
|
-
createAppOwner,
|
|
48
|
-
createOwnerRow,
|
|
49
|
-
OwnerRow,
|
|
50
|
-
OwnerWithWriteAccess,
|
|
51
|
-
} from "./Owner.js";
|
|
45
|
+
import { AppOwner, createAppOwner, OwnerId, WriteKey } from "./Owner.js";
|
|
52
46
|
import {
|
|
53
47
|
applyProtocolMessageAsClient,
|
|
54
48
|
Base64Url256,
|
|
55
49
|
BinaryId,
|
|
56
50
|
binaryIdToId,
|
|
57
51
|
BinaryOwnerId,
|
|
52
|
+
binaryOwnerIdToOwnerId,
|
|
58
53
|
CrdtMessage,
|
|
59
54
|
createProtocolMessageForSync,
|
|
60
55
|
createProtocolMessageFromCrdtMessages,
|
|
@@ -83,6 +78,7 @@ import {
|
|
|
83
78
|
import { CreateSyncDep, SyncConfig, SyncDep } from "./Sync.js";
|
|
84
79
|
import {
|
|
85
80
|
binaryTimestampToTimestamp,
|
|
81
|
+
createInitialTimestamp,
|
|
86
82
|
receiveTimestamp,
|
|
87
83
|
sendTimestamp,
|
|
88
84
|
Timestamp,
|
|
@@ -90,6 +86,7 @@ import {
|
|
|
90
86
|
TimestampCounterOverflowError,
|
|
91
87
|
TimestampDriftError,
|
|
92
88
|
TimestampError,
|
|
89
|
+
TimestampString,
|
|
93
90
|
timestampStringToTimestamp,
|
|
94
91
|
TimestampTimeOutOfRangeError,
|
|
95
92
|
timestampToBinaryTimestamp,
|
|
@@ -159,7 +156,7 @@ export type DbWorkerInput =
|
|
|
159
156
|
export type DbWorkerOutput =
|
|
160
157
|
| {
|
|
161
158
|
readonly type: "onInit";
|
|
162
|
-
readonly
|
|
159
|
+
readonly appOwner: AppOwner;
|
|
163
160
|
}
|
|
164
161
|
| {
|
|
165
162
|
readonly type: "onError";
|
|
@@ -209,21 +206,53 @@ type DbWorkerDeps = Omit<
|
|
|
209
206
|
TimestampConfigDep &
|
|
210
207
|
SymmetricCryptoDep &
|
|
211
208
|
PostMessageDep &
|
|
212
|
-
|
|
209
|
+
OwnersDep &
|
|
210
|
+
ClockDep &
|
|
213
211
|
GetQueryRowsCacheDep &
|
|
214
212
|
ClientStorageDep;
|
|
215
213
|
|
|
216
214
|
type PostMessageDep = WorkerPostMessageDep<DbWorkerOutput>;
|
|
217
215
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
216
|
+
interface OwnersDep {
|
|
217
|
+
readonly owners: Owners;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
type Owners = Map<OwnerId, AppOwner>;
|
|
221
|
+
|
|
222
|
+
interface ClockDep {
|
|
223
|
+
readonly clock: Clock;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface Clock {
|
|
227
|
+
readonly get: () => Timestamp;
|
|
228
|
+
readonly save: (timestamp: Timestamp) => Result<void, SqliteError>;
|
|
221
229
|
}
|
|
222
230
|
|
|
223
231
|
interface GetQueryRowsCacheDep {
|
|
224
232
|
readonly getQueryRowsCache: (tabId: Id) => QueryRowsCache;
|
|
225
233
|
}
|
|
226
234
|
|
|
235
|
+
const createClock =
|
|
236
|
+
(deps: NanoIdLibDep & SqliteDep) =>
|
|
237
|
+
(initialTimestamp = createInitialTimestamp(deps)): Clock => {
|
|
238
|
+
let currentTimestamp = initialTimestamp;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
get: () => currentTimestamp,
|
|
242
|
+
save: (timestamp) => {
|
|
243
|
+
currentTimestamp = timestamp;
|
|
244
|
+
|
|
245
|
+
const timestampString = timestampToTimestampString(timestamp);
|
|
246
|
+
const saveTimestamp = deps.sqlite.exec(sql.prepared`
|
|
247
|
+
update evolu_config set "clock" = ${timestampString};
|
|
248
|
+
`);
|
|
249
|
+
if (!saveTimestamp.ok) return saveTimestamp;
|
|
250
|
+
|
|
251
|
+
return ok();
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
227
256
|
export const createDbWorkerForPlatform = (
|
|
228
257
|
platformDeps: DbWorkerPlatformDeps,
|
|
229
258
|
): DbWorker => {
|
|
@@ -245,35 +274,91 @@ export const createDbWorkerForPlatform = (
|
|
|
245
274
|
initMessage.config.name,
|
|
246
275
|
{ memory: initMessage.config.inMemory ?? false },
|
|
247
276
|
);
|
|
248
|
-
|
|
249
277
|
if (!sqliteResult.ok) {
|
|
250
278
|
postMessage({ type: "onError", error: sqliteResult.error });
|
|
251
279
|
return null;
|
|
252
280
|
}
|
|
281
|
+
|
|
253
282
|
const sqlite = sqliteResult.value;
|
|
283
|
+
const platformDepsWithSqlite = { ...platformDeps, sqlite };
|
|
254
284
|
|
|
255
285
|
const deps = sqlite.transaction(() => {
|
|
256
286
|
const currentDbSchema = getDbSchema({ sqlite })();
|
|
257
287
|
if (!currentDbSchema.ok) return currentDbSchema;
|
|
258
288
|
|
|
289
|
+
let appOwner: AppOwner;
|
|
290
|
+
let clock: Clock;
|
|
291
|
+
|
|
292
|
+
const versionTableExists = currentDbSchema.value.tables.some(
|
|
293
|
+
(table) => table.name === "evolu_version",
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (versionTableExists) {
|
|
297
|
+
const versionResult = sqlite.exec<{
|
|
298
|
+
protocolVersion: number;
|
|
299
|
+
}>(sql`select protocolVersion from evolu_version limit 1;`);
|
|
300
|
+
if (!versionResult.ok) return versionResult;
|
|
301
|
+
|
|
302
|
+
// TODO: Handle version migrations here if needed
|
|
303
|
+
// const [{ protocolVersion }] = protocolVersionResult.value.rows;
|
|
304
|
+
// if (protocolVersion < currentProtocolVersion) {
|
|
305
|
+
// const migrateResult = migrateDatabase({ sqlite })(
|
|
306
|
+
// protocolVersion,
|
|
307
|
+
// currentProtocolVersion
|
|
308
|
+
// );
|
|
309
|
+
// if (!migrateResult.ok) return migrateResult;
|
|
310
|
+
// }
|
|
311
|
+
|
|
312
|
+
const configResult = sqlite.exec<{
|
|
313
|
+
clock: TimestampString;
|
|
314
|
+
appOwnerId: OwnerId;
|
|
315
|
+
appOwnerEncryptionKey: EncryptionKey;
|
|
316
|
+
appOwnerWriteKey: WriteKey;
|
|
317
|
+
appOwnerMnemonic: Mnemonic | null;
|
|
318
|
+
}>(sql`
|
|
319
|
+
select
|
|
320
|
+
clock,
|
|
321
|
+
appOwnerId,
|
|
322
|
+
appOwnerEncryptionKey,
|
|
323
|
+
appOwnerWriteKey,
|
|
324
|
+
appOwnerMnemonic
|
|
325
|
+
from evolu_config
|
|
326
|
+
limit 1;
|
|
327
|
+
`);
|
|
328
|
+
if (!configResult.ok) return configResult;
|
|
329
|
+
|
|
330
|
+
const [config] = configResult.value.rows;
|
|
331
|
+
|
|
332
|
+
appOwner = {
|
|
333
|
+
type: "AppOwner",
|
|
334
|
+
id: config.appOwnerId,
|
|
335
|
+
encryptionKey: config.appOwnerEncryptionKey,
|
|
336
|
+
writeKey: config.appOwnerWriteKey,
|
|
337
|
+
mnemonic: config.appOwnerMnemonic,
|
|
338
|
+
};
|
|
339
|
+
clock = createClock(platformDepsWithSqlite)(
|
|
340
|
+
timestampStringToTimestamp(config.clock),
|
|
341
|
+
);
|
|
342
|
+
} else {
|
|
343
|
+
appOwner =
|
|
344
|
+
initMessage.config.initialAppOwner ??
|
|
345
|
+
createAppOwner(platformDeps.createMnemonic());
|
|
346
|
+
clock = createClock(platformDepsWithSqlite)();
|
|
347
|
+
const initializeDbResult = initializeDb(platformDepsWithSqlite)(
|
|
348
|
+
appOwner,
|
|
349
|
+
clock,
|
|
350
|
+
);
|
|
351
|
+
if (!initializeDbResult.ok) return initializeDbResult;
|
|
352
|
+
}
|
|
353
|
+
|
|
259
354
|
const ensureDbSchemaResult = ensureDbSchema({ sqlite })(
|
|
260
355
|
initMessage.dbSchema,
|
|
261
356
|
currentDbSchema.value,
|
|
262
357
|
);
|
|
263
358
|
if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
264
359
|
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const appOwnerAndOwnerRow = ownerExists
|
|
270
|
-
? selectAppOwner({ sqlite })
|
|
271
|
-
: initializeDb({ ...platformDeps, sqlite })(
|
|
272
|
-
initMessage.config.mnemonic,
|
|
273
|
-
);
|
|
274
|
-
if (!appOwnerAndOwnerRow.ok) return appOwnerAndOwnerRow;
|
|
275
|
-
|
|
276
|
-
const [appOwner, ownerRow] = appOwnerAndOwnerRow.value;
|
|
360
|
+
const owners: Owners = new Map();
|
|
361
|
+
owners.set(appOwner.id, appOwner);
|
|
277
362
|
|
|
278
363
|
const depsWithoutSyncAndStorage = {
|
|
279
364
|
...platformDeps,
|
|
@@ -282,7 +367,8 @@ export const createDbWorkerForPlatform = (
|
|
|
282
367
|
timestampConfig: initMessage.config,
|
|
283
368
|
symmetricCrypto: createSymmetricCrypto(platformDeps),
|
|
284
369
|
getQueryRowsCache,
|
|
285
|
-
|
|
370
|
+
clock,
|
|
371
|
+
owners,
|
|
286
372
|
};
|
|
287
373
|
|
|
288
374
|
const storage = createClientStorage(depsWithoutSyncAndStorage)({
|
|
@@ -297,12 +383,15 @@ export const createDbWorkerForPlatform = (
|
|
|
297
383
|
storage: storage.value,
|
|
298
384
|
};
|
|
299
385
|
|
|
300
|
-
if (
|
|
386
|
+
if (
|
|
387
|
+
!versionTableExists &&
|
|
388
|
+
isNonEmptyReadonlyArray(initMessage.initialData)
|
|
389
|
+
) {
|
|
301
390
|
const result = applyChanges(depsWithoutSync)(initMessage.initialData);
|
|
302
391
|
if (!result.ok) return result;
|
|
303
392
|
}
|
|
304
393
|
|
|
305
|
-
postMessage({ type: "onInit",
|
|
394
|
+
postMessage({ type: "onInit", appOwner });
|
|
306
395
|
|
|
307
396
|
const sync = platformDeps.createSync(platformDeps)({
|
|
308
397
|
...initMessage.config,
|
|
@@ -397,15 +486,11 @@ export const createDbWorkerForPlatform = (
|
|
|
397
486
|
const messages = applyChanges(deps)(toSyncChanges, onChange);
|
|
398
487
|
if (!messages.ok) return messages;
|
|
399
488
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// error if not.
|
|
403
|
-
if (owner.writeKey == null) {
|
|
404
|
-
return ok();
|
|
405
|
-
}
|
|
489
|
+
// TODO: Use owner from db change or AppOwner
|
|
490
|
+
const owner = Array.from(deps.owners.values())[0];
|
|
406
491
|
|
|
407
492
|
const protocolMessage = createProtocolMessageFromCrdtMessages(deps)(
|
|
408
|
-
owner
|
|
493
|
+
owner,
|
|
409
494
|
messages.value,
|
|
410
495
|
);
|
|
411
496
|
|
|
@@ -460,9 +545,10 @@ export const createDbWorkerForPlatform = (
|
|
|
460
545
|
);
|
|
461
546
|
if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
462
547
|
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
548
|
+
const appOwner = createAppOwner(message.restore.mnemonic);
|
|
549
|
+
const clock = createClock(deps)();
|
|
550
|
+
|
|
551
|
+
const initializeDbResult = initializeDb(deps)(appOwner, clock);
|
|
466
552
|
if (!initializeDbResult.ok) return initializeDbResult;
|
|
467
553
|
}
|
|
468
554
|
return ok();
|
|
@@ -712,57 +798,54 @@ export const createAppTable = (
|
|
|
712
798
|
);
|
|
713
799
|
` as SafeSql;
|
|
714
800
|
|
|
715
|
-
const selectAppOwner = (
|
|
716
|
-
deps: SqliteDep,
|
|
717
|
-
): Result<[AppOwner, OwnerRow], SqliteError> => {
|
|
718
|
-
const result = deps.sqlite.exec<OwnerRow>(sql`
|
|
719
|
-
select mnemonic, id, createdAt, encryptionKey, writeKey, timestamp
|
|
720
|
-
from evolu_owner
|
|
721
|
-
order by createdAt asc
|
|
722
|
-
limit 1;
|
|
723
|
-
`);
|
|
724
|
-
|
|
725
|
-
if (!result.ok) return result;
|
|
726
|
-
|
|
727
|
-
const {
|
|
728
|
-
rows: [ownerRow],
|
|
729
|
-
} = result.value;
|
|
730
|
-
|
|
731
|
-
assert(ownerRow.writeKey != null, "The writeKey is null");
|
|
732
|
-
|
|
733
|
-
const appOwner: AppOwner = {
|
|
734
|
-
type: "AppOwner",
|
|
735
|
-
mnemonic: ownerRow.mnemonic,
|
|
736
|
-
createdAt: ownerRow.createdAt,
|
|
737
|
-
id: ownerRow.id,
|
|
738
|
-
encryptionKey: ownerRow.encryptionKey,
|
|
739
|
-
writeKey: ownerRow.writeKey,
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
return ok([appOwner, ownerRow]);
|
|
743
|
-
};
|
|
744
|
-
|
|
745
801
|
const initializeDb =
|
|
802
|
+
(deps: SqliteDep & CreateMnemonicDep & TimeDep & CreateRandomBytesDep) =>
|
|
746
803
|
(
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
TimeDep &
|
|
751
|
-
CreateRandomBytesDep,
|
|
752
|
-
) =>
|
|
753
|
-
(mnemonic?: Mnemonic): Result<[AppOwner, OwnerRow], SqliteError> => {
|
|
804
|
+
initialAppOwner: AppOwner,
|
|
805
|
+
initialClock: Clock,
|
|
806
|
+
): Result<void, SqliteError> => {
|
|
754
807
|
for (const query of [
|
|
808
|
+
// Never change structure to ensure all versions can read it.
|
|
809
|
+
sql`
|
|
810
|
+
create table evolu_version (
|
|
811
|
+
"protocolVersion" integer not null
|
|
812
|
+
)
|
|
813
|
+
strict;
|
|
814
|
+
`,
|
|
815
|
+
|
|
816
|
+
sql`
|
|
817
|
+
insert into evolu_version ("protocolVersion")
|
|
818
|
+
values (${protocolVersion});
|
|
819
|
+
`,
|
|
820
|
+
|
|
755
821
|
sql`
|
|
756
822
|
create table evolu_config (
|
|
757
|
-
"
|
|
758
|
-
"
|
|
823
|
+
"clock" text not null,
|
|
824
|
+
"appOwnerId" text not null,
|
|
825
|
+
"appOwnerEncryptionKey" blob not null,
|
|
826
|
+
"appOwnerWriteKey" blob not null,
|
|
827
|
+
"appOwnerMnemonic" text
|
|
759
828
|
)
|
|
760
829
|
strict;
|
|
761
830
|
`,
|
|
762
831
|
|
|
763
832
|
sql`
|
|
764
|
-
insert into evolu_config
|
|
765
|
-
|
|
833
|
+
insert into evolu_config
|
|
834
|
+
(
|
|
835
|
+
"clock",
|
|
836
|
+
"appOwnerId",
|
|
837
|
+
"appOwnerEncryptionKey",
|
|
838
|
+
"appOwnerWriteKey",
|
|
839
|
+
"appOwnerMnemonic"
|
|
840
|
+
)
|
|
841
|
+
values
|
|
842
|
+
(
|
|
843
|
+
${timestampToTimestampString(initialClock.get())},
|
|
844
|
+
${initialAppOwner.id},
|
|
845
|
+
${initialAppOwner.encryptionKey},
|
|
846
|
+
${initialAppOwner.writeKey},
|
|
847
|
+
${initialAppOwner.mnemonic ?? null}
|
|
848
|
+
);
|
|
766
849
|
`,
|
|
767
850
|
|
|
768
851
|
/**
|
|
@@ -802,50 +885,12 @@ const initializeDb =
|
|
|
802
885
|
"timestamp" desc
|
|
803
886
|
);
|
|
804
887
|
`,
|
|
805
|
-
|
|
806
|
-
sql`
|
|
807
|
-
create table evolu_owner (
|
|
808
|
-
"mnemonic" text not null primary key,
|
|
809
|
-
"id" text not null,
|
|
810
|
-
"encryptionKey" blob not null,
|
|
811
|
-
"createdAt" text not null,
|
|
812
|
-
"writeKey" blob,
|
|
813
|
-
"timestamp" text not null
|
|
814
|
-
)
|
|
815
|
-
strict;
|
|
816
|
-
`,
|
|
817
888
|
]) {
|
|
818
889
|
const result = deps.sqlite.exec(query);
|
|
819
890
|
if (!result.ok) return result;
|
|
820
891
|
}
|
|
821
892
|
|
|
822
|
-
|
|
823
|
-
const ownerRow = createOwnerRow(deps)(appOwner);
|
|
824
|
-
|
|
825
|
-
const result = deps.sqlite.exec(sql`
|
|
826
|
-
insert into evolu_owner
|
|
827
|
-
(
|
|
828
|
-
"mnemonic",
|
|
829
|
-
"id",
|
|
830
|
-
"encryptionKey",
|
|
831
|
-
"createdAt",
|
|
832
|
-
"writeKey",
|
|
833
|
-
"timestamp"
|
|
834
|
-
)
|
|
835
|
-
values
|
|
836
|
-
(
|
|
837
|
-
${ownerRow.mnemonic},
|
|
838
|
-
${ownerRow.id},
|
|
839
|
-
${ownerRow.encryptionKey},
|
|
840
|
-
${ownerRow.createdAt},
|
|
841
|
-
${ownerRow.writeKey},
|
|
842
|
-
${ownerRow.timestamp}
|
|
843
|
-
);
|
|
844
|
-
`);
|
|
845
|
-
|
|
846
|
-
if (!result.ok) return result;
|
|
847
|
-
|
|
848
|
-
return ok([appOwner, ownerRow]);
|
|
893
|
+
return ok();
|
|
849
894
|
};
|
|
850
895
|
|
|
851
896
|
const applyChanges =
|
|
@@ -854,8 +899,9 @@ const applyChanges =
|
|
|
854
899
|
TimeDep &
|
|
855
900
|
TimestampConfigDep &
|
|
856
901
|
RandomDep &
|
|
857
|
-
|
|
858
|
-
|
|
902
|
+
ClientStorageDep &
|
|
903
|
+
OwnersDep &
|
|
904
|
+
ClockDep,
|
|
859
905
|
) =>
|
|
860
906
|
(
|
|
861
907
|
changes: NonEmptyReadonlyArray<DbChange>,
|
|
@@ -867,20 +913,18 @@ const applyChanges =
|
|
|
867
913
|
| TimestampCounterOverflowError
|
|
868
914
|
| SqliteError
|
|
869
915
|
> => {
|
|
870
|
-
let
|
|
871
|
-
deps.ownerRowRef.get().timestamp,
|
|
872
|
-
);
|
|
916
|
+
let clockTimestamp = deps.clock.get();
|
|
873
917
|
|
|
874
918
|
const messages: Array<CrdtMessage> = [];
|
|
875
919
|
|
|
876
920
|
for (const change of changes) {
|
|
877
|
-
const nextTimestamp = sendTimestamp(deps)(
|
|
921
|
+
const nextTimestamp = sendTimestamp(deps)(clockTimestamp);
|
|
878
922
|
if (!nextTimestamp.ok) return nextTimestamp;
|
|
879
|
-
|
|
880
|
-
messages.push({ timestamp:
|
|
923
|
+
clockTimestamp = nextTimestamp.value;
|
|
924
|
+
messages.push({ timestamp: clockTimestamp, change });
|
|
881
925
|
}
|
|
882
926
|
|
|
883
|
-
const apply = applyMessages(deps)(messages,
|
|
927
|
+
const apply = applyMessages(deps)(messages, clockTimestamp);
|
|
884
928
|
if (!apply.ok) return apply;
|
|
885
929
|
|
|
886
930
|
if (onChange) onChange();
|
|
@@ -890,12 +934,14 @@ const applyChanges =
|
|
|
890
934
|
};
|
|
891
935
|
|
|
892
936
|
const applyMessages =
|
|
893
|
-
(deps: SqliteDep & RandomDep &
|
|
937
|
+
(deps: SqliteDep & RandomDep & ClientStorageDep & ClockDep & OwnersDep) =>
|
|
894
938
|
(
|
|
895
939
|
messages: ReadonlyArray<CrdtMessage>,
|
|
896
|
-
|
|
940
|
+
clockTimestamp: Timestamp,
|
|
897
941
|
): Result<void, SqliteError> => {
|
|
898
|
-
const ownerId = ownerIdToBinaryOwnerId(
|
|
942
|
+
const ownerId = ownerIdToBinaryOwnerId(
|
|
943
|
+
Array.from(deps.owners.values())[0].id,
|
|
944
|
+
);
|
|
899
945
|
|
|
900
946
|
for (const message of messages) {
|
|
901
947
|
const result1 = applyMessageToAppTable(deps)(ownerId, message);
|
|
@@ -908,18 +954,11 @@ const applyMessages =
|
|
|
908
954
|
if (!result2.ok) return result2;
|
|
909
955
|
}
|
|
910
956
|
|
|
911
|
-
|
|
912
|
-
deps.ownerRowRef.modify((owner) => ({ ...owner, timestamp }));
|
|
913
|
-
const saveTimestamp = deps.sqlite.exec(sql.prepared`
|
|
914
|
-
update evolu_owner set "timestamp" = ${timestamp};
|
|
915
|
-
`);
|
|
916
|
-
if (!saveTimestamp.ok) return saveTimestamp;
|
|
917
|
-
|
|
918
|
-
return ok();
|
|
957
|
+
return deps.clock.save(clockTimestamp);
|
|
919
958
|
};
|
|
920
959
|
|
|
921
960
|
const applyMessageToAppTable =
|
|
922
|
-
(deps: SqliteDep
|
|
961
|
+
(deps: SqliteDep) =>
|
|
923
962
|
(ownerId: BinaryOwnerId, message: CrdtMessage): Result<void, SqliteError> => {
|
|
924
963
|
const timestamp = timestampToBinaryTimestamp(message.timestamp);
|
|
925
964
|
const updatedAt = new Date(message.timestamp.millis).toISOString();
|
|
@@ -1046,11 +1085,11 @@ const dropAllTables = (deps: SqliteDep): Result<void, SqliteError> => {
|
|
|
1046
1085
|
};
|
|
1047
1086
|
|
|
1048
1087
|
const handleSyncOpen =
|
|
1049
|
-
(deps:
|
|
1088
|
+
(deps: StorageDep & ConsoleDep & OwnersDep): SyncConfig["onOpen"] =>
|
|
1050
1089
|
(send) => {
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1090
|
+
for (const [id] of deps.owners) {
|
|
1091
|
+
const message = createProtocolMessageForSync(deps)(id);
|
|
1092
|
+
if (!message) return;
|
|
1054
1093
|
deps.console.log("[db]", "send initial sync message", message);
|
|
1055
1094
|
send(message);
|
|
1056
1095
|
}
|
|
@@ -1058,15 +1097,14 @@ const handleSyncOpen =
|
|
|
1058
1097
|
|
|
1059
1098
|
const createHandleSyncMessage =
|
|
1060
1099
|
(
|
|
1061
|
-
deps: PostMessageDep & StorageDep & SqliteDep & ConsoleDep &
|
|
1100
|
+
deps: PostMessageDep & StorageDep & SqliteDep & ConsoleDep & OwnersDep,
|
|
1062
1101
|
): SyncConfig["onMessage"] =>
|
|
1063
1102
|
(input, send) => {
|
|
1064
1103
|
deps.console.log("[db]", "receive sync message", input);
|
|
1065
|
-
const { writeKey } = deps.ownerRowRef.get();
|
|
1066
1104
|
|
|
1067
1105
|
const output = deps.sqlite.transaction(() =>
|
|
1068
1106
|
applyProtocolMessageAsClient(deps)(input, {
|
|
1069
|
-
getWriteKey: (
|
|
1107
|
+
getWriteKey: (ownerId) => deps.owners.get(ownerId)?.writeKey ?? null,
|
|
1070
1108
|
}),
|
|
1071
1109
|
);
|
|
1072
1110
|
if (!output.ok) {
|
|
@@ -1080,20 +1118,21 @@ const createHandleSyncMessage =
|
|
|
1080
1118
|
}
|
|
1081
1119
|
};
|
|
1082
1120
|
|
|
1083
|
-
export interface ClientStorage extends SqliteStorageBase, Storage {}
|
|
1084
|
-
|
|
1085
1121
|
export interface ClientStorageDep {
|
|
1086
1122
|
readonly storage: ClientStorage;
|
|
1087
1123
|
}
|
|
1088
1124
|
|
|
1125
|
+
export interface ClientStorage extends SqliteStorageBase, Storage {}
|
|
1126
|
+
|
|
1089
1127
|
const createClientStorage =
|
|
1090
1128
|
(
|
|
1091
1129
|
deps: SqliteDep &
|
|
1092
1130
|
PostMessageDep &
|
|
1093
1131
|
SymmetricCryptoDep &
|
|
1094
|
-
OwnerRowRefDep &
|
|
1095
1132
|
RandomDep &
|
|
1096
1133
|
TimeDep &
|
|
1134
|
+
OwnersDep &
|
|
1135
|
+
ClockDep &
|
|
1097
1136
|
TimestampConfigDep,
|
|
1098
1137
|
) =>
|
|
1099
1138
|
(
|
|
@@ -1108,9 +1147,10 @@ const createClientStorage =
|
|
|
1108
1147
|
validateWriteKey: constFalse,
|
|
1109
1148
|
setWriteKey: constFalse,
|
|
1110
1149
|
|
|
1111
|
-
writeMessages: (
|
|
1112
|
-
|
|
1113
|
-
|
|
1150
|
+
writeMessages: (ownerId, messages) => {
|
|
1151
|
+
const owner = deps.owners.get(binaryOwnerIdToOwnerId(ownerId));
|
|
1152
|
+
assert(owner, "Missing owner");
|
|
1153
|
+
|
|
1114
1154
|
const decodedAndDecryptedMessages: Array<CrdtMessage> = [];
|
|
1115
1155
|
|
|
1116
1156
|
for (const message of messages) {
|
|
@@ -1133,20 +1173,23 @@ const createClientStorage =
|
|
|
1133
1173
|
});
|
|
1134
1174
|
}
|
|
1135
1175
|
|
|
1136
|
-
let
|
|
1176
|
+
let clockTimestamp = deps.clock.get();
|
|
1137
1177
|
|
|
1138
1178
|
for (const message of messages) {
|
|
1139
|
-
const receive = receiveTimestamp(deps)(
|
|
1179
|
+
const receive = receiveTimestamp(deps)(
|
|
1180
|
+
clockTimestamp,
|
|
1181
|
+
message.timestamp,
|
|
1182
|
+
);
|
|
1140
1183
|
if (!receive.ok) {
|
|
1141
1184
|
deps.postMessage({ type: "onError", error: receive.error });
|
|
1142
1185
|
return false;
|
|
1143
1186
|
}
|
|
1144
|
-
|
|
1187
|
+
clockTimestamp = receive.value;
|
|
1145
1188
|
}
|
|
1146
1189
|
|
|
1147
1190
|
const applyMessagesResult = applyMessages({ ...deps, storage })(
|
|
1148
1191
|
decodedAndDecryptedMessages,
|
|
1149
|
-
|
|
1192
|
+
clockTimestamp,
|
|
1150
1193
|
);
|
|
1151
1194
|
|
|
1152
1195
|
if (!applyMessagesResult.ok) {
|
|
@@ -1163,6 +1206,9 @@ const createClientStorage =
|
|
|
1163
1206
|
},
|
|
1164
1207
|
|
|
1165
1208
|
readDbChange: (ownerId, timestamp) => {
|
|
1209
|
+
const owner = deps.owners.get(binaryOwnerIdToOwnerId(ownerId));
|
|
1210
|
+
assert(owner, "Missing owner");
|
|
1211
|
+
|
|
1166
1212
|
const result = deps.sqlite.exec<{
|
|
1167
1213
|
table: Base64Url256;
|
|
1168
1214
|
id: BinaryId;
|
|
@@ -1198,9 +1244,8 @@ const createClientStorage =
|
|
|
1198
1244
|
values,
|
|
1199
1245
|
},
|
|
1200
1246
|
};
|
|
1201
|
-
const { encryptionKey } = deps.ownerRowRef.get();
|
|
1202
1247
|
|
|
1203
|
-
return encodeAndEncryptDbChange(deps)(message, encryptionKey);
|
|
1248
|
+
return encodeAndEncryptDbChange(deps)(message, owner.encryptionKey);
|
|
1204
1249
|
},
|
|
1205
1250
|
};
|
|
1206
1251
|
|
package/src/Evolu/Evolu.ts
CHANGED
|
@@ -583,11 +583,13 @@ const createEvoluInstance =
|
|
|
583
583
|
|
|
584
584
|
deps.console.log("[evolu]", "createEvoluInstance");
|
|
585
585
|
|
|
586
|
+
// evoluConfig.mnemonic
|
|
587
|
+
|
|
586
588
|
const { initialData, indexes, ...config } = evoluConfig;
|
|
587
589
|
|
|
588
590
|
const errorStore = createStore<EvoluError | null>(null);
|
|
589
591
|
const rowsStore = createStore<QueryRowsMap>(new Map());
|
|
590
|
-
const
|
|
592
|
+
const appOwnerStore = createStore<AppOwner | null>(null);
|
|
591
593
|
const syncStore = createStore<SyncState>(initialSyncState);
|
|
592
594
|
|
|
593
595
|
const subscribedQueries = createSubscribedQueries(rowsStore);
|
|
@@ -605,7 +607,7 @@ const createEvoluInstance =
|
|
|
605
607
|
dbWorker.onMessage((message) => {
|
|
606
608
|
switch (message.type) {
|
|
607
609
|
case "onInit": {
|
|
608
|
-
|
|
610
|
+
appOwnerStore.set(message.appOwner);
|
|
609
611
|
break;
|
|
610
612
|
}
|
|
611
613
|
|
|
@@ -907,8 +909,8 @@ const createEvoluInstance =
|
|
|
907
909
|
getQueryRows: <R extends Row>(query: Query<R>): QueryRows<R> =>
|
|
908
910
|
(rowsStore.get().get(query) ?? emptyRows) as QueryRows<R>,
|
|
909
911
|
|
|
910
|
-
subscribeAppOwner:
|
|
911
|
-
getAppOwner:
|
|
912
|
+
subscribeAppOwner: appOwnerStore.subscribe,
|
|
913
|
+
getAppOwner: appOwnerStore.get,
|
|
912
914
|
|
|
913
915
|
subscribeSyncState: syncStore.subscribe,
|
|
914
916
|
getSyncState: syncStore.get,
|