@evolu/common 7.2.3 → 7.3.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/dist/src/Function.d.ts +61 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +19 -0
- package/dist/src/Object.d.ts +16 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +16 -0
- package/dist/src/Result.d.ts +36 -29
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Sqlite.d.ts +9 -7
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +8 -0
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +6 -4
- package/dist/src/Type.d.ts +30 -2
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +82 -0
- package/dist/src/local-first/Db.d.ts +1 -5
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +109 -108
- package/dist/src/local-first/Evolu.d.ts +3 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +6 -7
- package/dist/src/local-first/LocalAuth.d.ts +2 -2
- package/dist/src/local-first/Owner.d.ts +3 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +10 -10
- package/dist/src/local-first/PublicKysely.js +1 -1
- package/dist/src/local-first/Query.d.ts +54 -6
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +130 -11
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +17 -18
- package/dist/src/local-first/Schema.d.ts +15 -14
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +43 -51
- package/dist/src/local-first/Storage.d.ts +2 -2
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +2 -6
- package/dist/src/local-first/Sync.d.ts +9 -4
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +132 -59
- package/dist/src/local-first/index.d.ts +0 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +0 -1
- package/package.json +1 -1
- package/src/Function.ts +74 -0
- package/src/Object.ts +20 -0
- package/src/Result.ts +38 -29
- package/src/Sqlite.ts +18 -7
- package/src/Task.ts +6 -4
- package/src/Type.ts +134 -1
- package/src/local-first/Db.ts +181 -209
- package/src/local-first/Evolu.ts +9 -6
- package/src/local-first/LocalAuth.ts +2 -2
- package/src/local-first/Owner.ts +4 -0
- package/src/local-first/Protocol.ts +10 -16
- package/src/local-first/PublicKysely.ts +1 -1
- package/src/local-first/Query.ts +216 -21
- package/src/local-first/Relay.ts +20 -24
- package/src/local-first/Schema.ts +83 -70
- package/src/local-first/Storage.ts +3 -8
- package/src/local-first/Sync.ts +190 -66
- package/src/local-first/index.ts +0 -1
- package/dist/src/local-first/Diff.d.ts +0 -43
- package/dist/src/local-first/Diff.d.ts.map +0 -1
- package/dist/src/local-first/Diff.js +0 -97
- package/src/local-first/Diff.ts +0 -144
package/src/local-first/Db.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
firstInArray,
|
|
3
|
+
isNonEmptyArray,
|
|
4
|
+
NonEmptyReadonlyArray,
|
|
5
|
+
} from "../Array.js";
|
|
6
|
+
import { assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
2
7
|
import { CallbackId } from "../Callbacks.js";
|
|
3
8
|
import { ConsoleConfig, ConsoleDep } from "../Console.js";
|
|
4
9
|
import {
|
|
@@ -13,7 +18,6 @@ import { ok, Result } from "../Result.js";
|
|
|
13
18
|
import {
|
|
14
19
|
createSqlite,
|
|
15
20
|
CreateSqliteDriverDep,
|
|
16
|
-
explainSqliteQueryPlan,
|
|
17
21
|
sql,
|
|
18
22
|
SqliteDep,
|
|
19
23
|
SqliteError,
|
|
@@ -26,9 +30,9 @@ import {
|
|
|
26
30
|
MessageHandlers,
|
|
27
31
|
Worker,
|
|
28
32
|
} from "../Worker.js";
|
|
29
|
-
import { makePatches, QueryPatches } from "./Diff.js";
|
|
30
33
|
import {
|
|
31
34
|
AppOwner,
|
|
35
|
+
AppOwnerDep,
|
|
32
36
|
createAppOwner,
|
|
33
37
|
createOwnerSecret,
|
|
34
38
|
createOwnerWebSocketTransport,
|
|
@@ -40,11 +44,11 @@ import {
|
|
|
40
44
|
} from "./Owner.js";
|
|
41
45
|
import { ProtocolError, protocolVersion } from "./Protocol.js";
|
|
42
46
|
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
createGetQueryRowsCache,
|
|
48
|
+
GetQueryRowsCacheDep,
|
|
49
|
+
loadQueries,
|
|
46
50
|
Query,
|
|
47
|
-
|
|
51
|
+
QueryPatches,
|
|
48
52
|
} from "./Query.js";
|
|
49
53
|
import {
|
|
50
54
|
DbSchema,
|
|
@@ -60,6 +64,7 @@ import {
|
|
|
60
64
|
createSync,
|
|
61
65
|
SyncDep,
|
|
62
66
|
SyncOwner,
|
|
67
|
+
tryApplyQuarantinedMessages,
|
|
63
68
|
} from "./Sync.js";
|
|
64
69
|
import {
|
|
65
70
|
Timestamp,
|
|
@@ -311,24 +316,16 @@ type DbWorkerDeps = Omit<
|
|
|
311
316
|
DbWorkerPlatformDeps,
|
|
312
317
|
keyof CreateSqliteDriverDep | keyof CreateWebSocketDep
|
|
313
318
|
> &
|
|
319
|
+
AppOwnerDep &
|
|
314
320
|
GetQueryRowsCacheDep &
|
|
315
321
|
PostMessageDep &
|
|
316
322
|
SqliteDep &
|
|
317
|
-
SyncDep
|
|
318
|
-
AppOwnerDep;
|
|
319
|
-
|
|
320
|
-
interface GetQueryRowsCacheDep {
|
|
321
|
-
readonly getQueryRowsCache: (tabId: Id) => QueryRowsCache;
|
|
322
|
-
}
|
|
323
|
+
SyncDep;
|
|
323
324
|
|
|
324
325
|
export interface PostMessageDep {
|
|
325
326
|
readonly postMessage: (message: DbWorkerOutput) => void;
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
export interface AppOwnerDep {
|
|
329
|
-
readonly appOwner: AppOwner;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
329
|
export const createDbWorkerForPlatform = (
|
|
333
330
|
platformDeps: DbWorkerPlatformDeps,
|
|
334
331
|
): DbWorker =>
|
|
@@ -337,146 +334,141 @@ export const createDbWorkerForPlatform = (
|
|
|
337
334
|
DbWorkerOutput,
|
|
338
335
|
DbWorkerDeps
|
|
339
336
|
>({
|
|
340
|
-
init:
|
|
341
|
-
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
const createInit =
|
|
345
|
-
(platformDeps: DbWorkerPlatformDeps) =>
|
|
346
|
-
async (
|
|
347
|
-
initMessage: Extract<DbWorkerInput, { type: "init" }>,
|
|
348
|
-
postMessage: (msg: DbWorkerOutput) => void,
|
|
349
|
-
): Promise<DbWorkerDeps | null> => {
|
|
350
|
-
platformDeps.console.enabled = initMessage.config.enableLogging ?? false;
|
|
351
|
-
|
|
352
|
-
const sqliteResult = await createSqlite(platformDeps)(
|
|
353
|
-
initMessage.config.name,
|
|
354
|
-
{
|
|
355
|
-
memory: initMessage.config.inMemory ?? false,
|
|
356
|
-
encryptionKey: initMessage.config.encryptionKey ?? undefined,
|
|
357
|
-
},
|
|
358
|
-
);
|
|
359
|
-
if (!sqliteResult.ok) {
|
|
360
|
-
postMessage({ type: "onError", error: sqliteResult.error });
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
const sqlite = sqliteResult.value;
|
|
364
|
-
|
|
365
|
-
const deps = sqlite.transaction(() => {
|
|
366
|
-
const currentDbSchema = getDbSchema({ sqlite })();
|
|
367
|
-
if (!currentDbSchema.ok) return currentDbSchema;
|
|
337
|
+
init: async (initMessage, postMessage) => {
|
|
338
|
+
platformDeps.console.enabled = initMessage.config.enableLogging ?? false;
|
|
368
339
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
(table) => table.name === "evolu_version",
|
|
340
|
+
const deps = await createDbWorkerDeps(
|
|
341
|
+
platformDeps,
|
|
342
|
+
initMessage,
|
|
343
|
+
postMessage,
|
|
374
344
|
);
|
|
375
345
|
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (!currentVersion.ok) return currentVersion;
|
|
381
|
-
|
|
382
|
-
const configResult = sqlite.exec<{
|
|
383
|
-
clock: TimestampBytes;
|
|
384
|
-
appOwnerId: OwnerId;
|
|
385
|
-
appOwnerEncryptionKey: OwnerEncryptionKey;
|
|
386
|
-
appOwnerWriteKey: OwnerWriteKey;
|
|
387
|
-
appOwnerMnemonic: Mnemonic | null;
|
|
388
|
-
}>(sql`
|
|
389
|
-
select
|
|
390
|
-
clock,
|
|
391
|
-
appOwnerId,
|
|
392
|
-
appOwnerEncryptionKey,
|
|
393
|
-
appOwnerWriteKey,
|
|
394
|
-
appOwnerMnemonic
|
|
395
|
-
from evolu_config
|
|
396
|
-
limit 1;
|
|
397
|
-
`);
|
|
398
|
-
if (!configResult.ok) return configResult;
|
|
399
|
-
|
|
400
|
-
const [config] = configResult.value.rows;
|
|
346
|
+
if (!deps.ok) {
|
|
347
|
+
postMessage({ type: "onError", error: deps.error });
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
401
350
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
writeKey: config.appOwnerWriteKey,
|
|
407
|
-
mnemonic: config.appOwnerMnemonic,
|
|
408
|
-
};
|
|
351
|
+
return deps.value;
|
|
352
|
+
},
|
|
353
|
+
handlers,
|
|
354
|
+
});
|
|
409
355
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
356
|
+
const createDbWorkerDeps = async (
|
|
357
|
+
platformDeps: DbWorkerPlatformDeps,
|
|
358
|
+
initMessage: Extract<DbWorkerInput, { type: "init" }>,
|
|
359
|
+
postMessage: (msg: DbWorkerOutput) => void,
|
|
360
|
+
) => {
|
|
361
|
+
const sqlite = await createSqlite(platformDeps)(initMessage.config.name, {
|
|
362
|
+
memory: initMessage.config.inMemory ?? false,
|
|
363
|
+
encryptionKey: initMessage.config.encryptionKey ?? undefined,
|
|
364
|
+
});
|
|
365
|
+
if (!sqlite.ok) return sqlite;
|
|
366
|
+
|
|
367
|
+
const deps = { ...platformDeps, sqlite: sqlite.value };
|
|
368
|
+
|
|
369
|
+
return deps.sqlite.transaction(() => {
|
|
370
|
+
const dbSchema = getDbSchema(deps)();
|
|
371
|
+
if (!dbSchema.ok) return dbSchema;
|
|
372
|
+
|
|
373
|
+
const dbIsInitialized = "evolu_version" in dbSchema.value.tables;
|
|
374
|
+
|
|
375
|
+
let appOwner: AppOwner;
|
|
376
|
+
let clock: Clock;
|
|
377
|
+
|
|
378
|
+
if (dbIsInitialized) {
|
|
379
|
+
const currentVersion = deps.sqlite.exec<{
|
|
380
|
+
protocolVersion: number;
|
|
381
|
+
}>(sql`select protocolVersion from evolu_version limit 1;`);
|
|
382
|
+
if (!currentVersion.ok) return currentVersion;
|
|
383
|
+
|
|
384
|
+
const configResult = deps.sqlite.exec<{
|
|
385
|
+
clock: TimestampBytes;
|
|
386
|
+
appOwnerId: OwnerId;
|
|
387
|
+
appOwnerEncryptionKey: OwnerEncryptionKey;
|
|
388
|
+
appOwnerWriteKey: OwnerWriteKey;
|
|
389
|
+
appOwnerMnemonic: Mnemonic | null;
|
|
390
|
+
}>(sql`
|
|
391
|
+
select
|
|
392
|
+
clock,
|
|
393
|
+
appOwnerId,
|
|
394
|
+
appOwnerEncryptionKey,
|
|
395
|
+
appOwnerWriteKey,
|
|
396
|
+
appOwnerMnemonic
|
|
397
|
+
from evolu_config
|
|
398
|
+
limit 1;
|
|
399
|
+
`);
|
|
400
|
+
if (!configResult.ok) return configResult;
|
|
401
|
+
|
|
402
|
+
assertNonEmptyReadonlyArray(configResult.value.rows);
|
|
403
|
+
const config = firstInArray(configResult.value.rows);
|
|
404
|
+
|
|
405
|
+
appOwner = {
|
|
406
|
+
type: "AppOwner",
|
|
407
|
+
id: config.appOwnerId,
|
|
408
|
+
encryptionKey: config.appOwnerEncryptionKey,
|
|
409
|
+
writeKey: config.appOwnerWriteKey,
|
|
410
|
+
mnemonic: config.appOwnerMnemonic,
|
|
411
|
+
};
|
|
417
412
|
|
|
418
|
-
|
|
413
|
+
clock = createClock(deps)(timestampBytesToTimestamp(config.clock));
|
|
414
|
+
} else {
|
|
415
|
+
appOwner =
|
|
416
|
+
initMessage.config.externalAppOwner ??
|
|
417
|
+
createAppOwner(createOwnerSecret(platformDeps));
|
|
419
418
|
|
|
420
|
-
|
|
421
|
-
if (!result.ok) return result;
|
|
422
|
-
}
|
|
419
|
+
clock = createClock(deps)();
|
|
423
420
|
|
|
424
|
-
const result =
|
|
425
|
-
initMessage.dbSchema,
|
|
426
|
-
currentDbSchema.value,
|
|
427
|
-
);
|
|
421
|
+
const result = initializeDb(deps)(appOwner, clock.get());
|
|
428
422
|
if (!result.ok) return result;
|
|
423
|
+
}
|
|
429
424
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
symmetricCrypto: createSymmetricCrypto(platformDeps),
|
|
435
|
-
timestampConfig: initMessage.config,
|
|
436
|
-
postMessage,
|
|
437
|
-
})({
|
|
438
|
-
appOwner,
|
|
439
|
-
transports: initMessage.config.transports,
|
|
440
|
-
onError: (error) => {
|
|
441
|
-
postMessage({ type: "onError", error });
|
|
442
|
-
},
|
|
443
|
-
onReceive: () => {
|
|
444
|
-
postMessage({ type: "refreshQueries" });
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
if (!sync.ok) return sync;
|
|
448
|
-
|
|
449
|
-
sync.value.useOwner(true, appOwner);
|
|
450
|
-
|
|
451
|
-
const tabQueryRowsCacheMap = new Map<Id, QueryRowsCache>();
|
|
452
|
-
const getQueryRowsCache = (tabId: Id) => {
|
|
453
|
-
let cache = tabQueryRowsCacheMap.get(tabId);
|
|
454
|
-
if (!cache) {
|
|
455
|
-
cache = createQueryRowsCache();
|
|
456
|
-
tabQueryRowsCacheMap.set(tabId, cache);
|
|
457
|
-
}
|
|
458
|
-
return cache;
|
|
459
|
-
};
|
|
425
|
+
{
|
|
426
|
+
const result = ensureDbSchema(deps)(initMessage.dbSchema, dbSchema.value);
|
|
427
|
+
if (!result.ok) return result;
|
|
428
|
+
}
|
|
460
429
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
sqlite,
|
|
466
|
-
sync: sync.value,
|
|
467
|
-
appOwner,
|
|
468
|
-
};
|
|
430
|
+
{
|
|
431
|
+
const result = ensureMessageQuarantineTable(deps);
|
|
432
|
+
if (!result.ok) return result;
|
|
433
|
+
}
|
|
469
434
|
|
|
470
|
-
|
|
435
|
+
const sync = createSync({
|
|
436
|
+
...deps,
|
|
437
|
+
clock,
|
|
438
|
+
symmetricCrypto: createSymmetricCrypto(platformDeps),
|
|
439
|
+
timestampConfig: initMessage.config,
|
|
440
|
+
dbSchema: initMessage.dbSchema,
|
|
441
|
+
})({
|
|
442
|
+
appOwner,
|
|
443
|
+
transports: initMessage.config.transports,
|
|
444
|
+
onError: (error) => {
|
|
445
|
+
postMessage({ type: "onError", error });
|
|
446
|
+
},
|
|
447
|
+
onReceive: () => {
|
|
448
|
+
postMessage({ type: "refreshQueries" });
|
|
449
|
+
},
|
|
471
450
|
});
|
|
451
|
+
if (!sync.ok) return sync;
|
|
472
452
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
453
|
+
{
|
|
454
|
+
const result = tryApplyQuarantinedMessages({
|
|
455
|
+
...deps,
|
|
456
|
+
dbSchema: initMessage.dbSchema,
|
|
457
|
+
})();
|
|
458
|
+
if (!result.ok) return result;
|
|
476
459
|
}
|
|
477
460
|
|
|
478
|
-
|
|
479
|
-
|
|
461
|
+
sync.value.useOwner(true, appOwner);
|
|
462
|
+
|
|
463
|
+
return ok({
|
|
464
|
+
...deps,
|
|
465
|
+
getQueryRowsCache: createGetQueryRowsCache(),
|
|
466
|
+
postMessage,
|
|
467
|
+
sync: sync.value,
|
|
468
|
+
appOwner,
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
};
|
|
480
472
|
|
|
481
473
|
const initializeDb =
|
|
482
474
|
(deps: SqliteDep) =>
|
|
@@ -548,7 +540,6 @@ const initializeDb =
|
|
|
548
540
|
`,
|
|
549
541
|
|
|
550
542
|
// Index for reading database changes by owner and timestamp.
|
|
551
|
-
// Timestamp always corresponds to a DbChange.
|
|
552
543
|
sql`
|
|
553
544
|
create index evolu_history_ownerId_timestamp on evolu_history (
|
|
554
545
|
"ownerId",
|
|
@@ -576,6 +567,41 @@ const initializeDb =
|
|
|
576
567
|
return ok();
|
|
577
568
|
};
|
|
578
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Ensures the quarantine table exists for storing messages with unknown schema.
|
|
572
|
+
*
|
|
573
|
+
* When a device receives sync messages containing tables or columns that don't
|
|
574
|
+
* exist in its current schema (e.g., from a newer app version), those messages
|
|
575
|
+
* are stored here instead of being discarded. This enables forward
|
|
576
|
+
* compatibility:
|
|
577
|
+
*
|
|
578
|
+
* 1. Unknown data is preserved and can be applied when the app is updated
|
|
579
|
+
* 2. Messages are still propagated to other devices that may understand them
|
|
580
|
+
* 3. Partial messages work - known columns go to app tables, unknown to quarantine
|
|
581
|
+
*
|
|
582
|
+
* The `union all` query in `readDbChange` combines `evolu_history` and this
|
|
583
|
+
* table, ensuring all data (known and unknown) is included when syncing to
|
|
584
|
+
* other devices.
|
|
585
|
+
*/
|
|
586
|
+
const ensureMessageQuarantineTable = (
|
|
587
|
+
deps: SqliteDep,
|
|
588
|
+
): Result<void, SqliteError> => {
|
|
589
|
+
const result = deps.sqlite.exec(sql`
|
|
590
|
+
create table if not exists evolu_message_quarantine (
|
|
591
|
+
"ownerId" blob not null,
|
|
592
|
+
"timestamp" blob not null,
|
|
593
|
+
"table" text not null,
|
|
594
|
+
"id" blob not null,
|
|
595
|
+
"column" text not null,
|
|
596
|
+
"value" any,
|
|
597
|
+
primary key ("ownerId", "timestamp", "table", "id", "column")
|
|
598
|
+
)
|
|
599
|
+
strict;
|
|
600
|
+
`);
|
|
601
|
+
if (!result.ok) return result;
|
|
602
|
+
return ok();
|
|
603
|
+
};
|
|
604
|
+
|
|
579
605
|
const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
580
606
|
getAppOwner: (deps) => () => {
|
|
581
607
|
deps.postMessage({
|
|
@@ -647,11 +673,11 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
647
673
|
},
|
|
648
674
|
|
|
649
675
|
reset: (deps) => (message) => {
|
|
650
|
-
const
|
|
676
|
+
const result = deps.sqlite.transaction(() => {
|
|
651
677
|
const dbSchema = getDbSchema(deps)();
|
|
652
678
|
if (!dbSchema.ok) return dbSchema;
|
|
653
679
|
|
|
654
|
-
for (const
|
|
680
|
+
for (const tableName in dbSchema.value.tables) {
|
|
655
681
|
/**
|
|
656
682
|
* The dropped table is completely removed from the database schema and
|
|
657
683
|
* the disk file. The table can not be recovered. All indices and
|
|
@@ -659,34 +685,27 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
659
685
|
* https://sqlite.org/lang_droptable.html
|
|
660
686
|
*/
|
|
661
687
|
const result = deps.sqlite.exec(sql`
|
|
662
|
-
drop table ${sql.identifier(
|
|
688
|
+
drop table ${sql.identifier(tableName)};
|
|
663
689
|
`);
|
|
664
690
|
if (!result.ok) return result;
|
|
665
691
|
}
|
|
666
692
|
|
|
667
693
|
if (message.restore) {
|
|
668
|
-
const
|
|
669
|
-
if (!
|
|
670
|
-
|
|
671
|
-
const ensureDbSchemaResult = ensureDbSchema(deps)(
|
|
672
|
-
message.restore.dbSchema,
|
|
673
|
-
dbSchema.value,
|
|
674
|
-
);
|
|
675
|
-
if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
694
|
+
const result = ensureDbSchema(deps)(message.restore.dbSchema);
|
|
695
|
+
if (!result.ok) return result;
|
|
676
696
|
|
|
677
697
|
const secret = mnemonicToOwnerSecret(message.restore.mnemonic);
|
|
678
698
|
const appOwner = createAppOwner(secret);
|
|
679
699
|
const clock = createClock(deps)();
|
|
680
700
|
|
|
681
|
-
|
|
682
|
-
if (!initializeDbResult.ok) return initializeDbResult;
|
|
701
|
+
return initializeDb(deps)(appOwner, clock.get());
|
|
683
702
|
}
|
|
684
703
|
|
|
685
704
|
return ok();
|
|
686
705
|
});
|
|
687
706
|
|
|
688
|
-
if (!
|
|
689
|
-
deps.postMessage({ type: "onError", error:
|
|
707
|
+
if (!result.ok) {
|
|
708
|
+
deps.postMessage({ type: "onError", error: result.error });
|
|
690
709
|
return;
|
|
691
710
|
}
|
|
692
711
|
|
|
@@ -698,21 +717,12 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
698
717
|
},
|
|
699
718
|
|
|
700
719
|
ensureDbSchema: (deps) => (message) => {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const ensureDbSchemaResult = ensureDbSchema(deps)(
|
|
706
|
-
message.dbSchema,
|
|
707
|
-
dbSchema.value,
|
|
708
|
-
);
|
|
709
|
-
if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
710
|
-
|
|
711
|
-
return ok();
|
|
712
|
-
});
|
|
720
|
+
const result = deps.sqlite.transaction(() =>
|
|
721
|
+
ensureDbSchema(deps)(message.dbSchema),
|
|
722
|
+
);
|
|
713
723
|
|
|
714
|
-
if (!
|
|
715
|
-
deps.postMessage({ type: "onError", error:
|
|
724
|
+
if (!result.ok) {
|
|
725
|
+
deps.postMessage({ type: "onError", error: result.error });
|
|
716
726
|
return;
|
|
717
727
|
}
|
|
718
728
|
},
|
|
@@ -736,41 +746,3 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
736
746
|
deps.sync.useOwner(message.use, message.owner);
|
|
737
747
|
},
|
|
738
748
|
};
|
|
739
|
-
|
|
740
|
-
const loadQueries =
|
|
741
|
-
(deps: GetQueryRowsCacheDep & SqliteDep) =>
|
|
742
|
-
(
|
|
743
|
-
tabId: Id,
|
|
744
|
-
queries: ReadonlyArray<Query>,
|
|
745
|
-
): Result<ReadonlyArray<QueryPatches>, SqliteError> => {
|
|
746
|
-
const queriesRows = [];
|
|
747
|
-
|
|
748
|
-
for (const query of queries) {
|
|
749
|
-
const sqlQuery = deserializeQuery(query);
|
|
750
|
-
const result = deps.sqlite.exec(sqlQuery);
|
|
751
|
-
if (!result.ok) return result;
|
|
752
|
-
|
|
753
|
-
queriesRows.push([query, result.value.rows] as const);
|
|
754
|
-
if (sqlQuery.options?.logExplainQueryPlan) {
|
|
755
|
-
explainSqliteQueryPlan(deps)(sqlQuery);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const queryRowsCache = deps.getQueryRowsCache(tabId);
|
|
760
|
-
|
|
761
|
-
const previousState = queryRowsCache.get();
|
|
762
|
-
queryRowsCache.set(queriesRows);
|
|
763
|
-
|
|
764
|
-
const currentState = queryRowsCache.get();
|
|
765
|
-
|
|
766
|
-
const queryPatchesArray = queries.map(
|
|
767
|
-
(query): QueryPatches => ({
|
|
768
|
-
query,
|
|
769
|
-
patches: makePatches(
|
|
770
|
-
previousState.get(query),
|
|
771
|
-
currentState.get(query) ?? emptyRows,
|
|
772
|
-
),
|
|
773
|
-
}),
|
|
774
|
-
);
|
|
775
|
-
return ok(queryPatchesArray);
|
|
776
|
-
};
|
package/src/local-first/Evolu.ts
CHANGED
|
@@ -37,11 +37,11 @@ import {
|
|
|
37
37
|
} from "../Type.js";
|
|
38
38
|
import { IntentionalNever } from "../Types.js";
|
|
39
39
|
import { CreateDbWorkerDep, DbConfig, defaultDbConfig } from "./Db.js";
|
|
40
|
-
import { applyPatches } from "./Diff.js";
|
|
41
40
|
import { AppOwner } from "./Owner.js";
|
|
42
41
|
import { FlushSyncDep, ReloadAppDep } from "./Platform.js";
|
|
43
42
|
import { ProtocolError } from "./Protocol.js";
|
|
44
43
|
import {
|
|
44
|
+
applyPatches,
|
|
45
45
|
createSubscribedQueries,
|
|
46
46
|
emptyRows,
|
|
47
47
|
Queries,
|
|
@@ -101,6 +101,9 @@ export interface EvoluConfig extends Partial<DbConfig> {
|
|
|
101
101
|
* URL to reload browser tabs after reset or restore.
|
|
102
102
|
*
|
|
103
103
|
* The default value is `/`.
|
|
104
|
+
*
|
|
105
|
+
* Note: This option will be moved to web platform deps in the next major
|
|
106
|
+
* version.
|
|
104
107
|
*/
|
|
105
108
|
readonly reloadUrl?: string;
|
|
106
109
|
}
|
|
@@ -836,15 +839,15 @@ const createEvoluInstance =
|
|
|
836
839
|
|
|
837
840
|
subscribeQuery: (query) => (listener) => {
|
|
838
841
|
// Call the listener only if the result has been changed.
|
|
839
|
-
let
|
|
842
|
+
let previousRows: unknown = null;
|
|
840
843
|
const unsubscribe = subscribedQueries.subscribe(query)(() => {
|
|
841
|
-
const
|
|
842
|
-
if (
|
|
843
|
-
|
|
844
|
+
const rows = evolu.getQueryRows(query);
|
|
845
|
+
if (previousRows === rows) return;
|
|
846
|
+
previousRows = rows;
|
|
844
847
|
listener();
|
|
845
848
|
});
|
|
846
849
|
return () => {
|
|
847
|
-
|
|
850
|
+
previousRows = null;
|
|
848
851
|
unsubscribe();
|
|
849
852
|
};
|
|
850
853
|
},
|
package/src/local-first/Owner.ts
CHANGED
|
@@ -188,6 +188,10 @@ export interface AppOwner extends Owner {
|
|
|
188
188
|
readonly mnemonic?: Mnemonic | null;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
export interface AppOwnerDep {
|
|
192
|
+
readonly appOwner: AppOwner;
|
|
193
|
+
}
|
|
194
|
+
|
|
191
195
|
/** Creates an {@link AppOwner} from an {@link OwnerSecret}. */
|
|
192
196
|
export const createAppOwner = (secret: OwnerSecret): AppOwner => ({
|
|
193
197
|
...createOwner(secret),
|
|
@@ -972,12 +972,9 @@ export const applyProtocolMessageAsClient =
|
|
|
972
972
|
const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
|
|
973
973
|
|
|
974
974
|
if (isNonEmptyReadonlyArray(messages)) {
|
|
975
|
-
const
|
|
976
|
-
ownerIdBytes,
|
|
977
|
-
messages,
|
|
978
|
-
);
|
|
975
|
+
const result = await deps.storage.writeMessages(ownerIdBytes, messages);
|
|
979
976
|
// Errors are handled by the Storage. Here we just stop syncing.
|
|
980
|
-
if (!
|
|
977
|
+
if (!result.ok) return ok({ type: "no-response" });
|
|
981
978
|
}
|
|
982
979
|
|
|
983
980
|
// Now: No writeKey, no sync.
|
|
@@ -1006,10 +1003,10 @@ export const applyProtocolMessageAsClient =
|
|
|
1006
1003
|
rangesMaxSize: options.rangesMaxSize,
|
|
1007
1004
|
});
|
|
1008
1005
|
|
|
1009
|
-
const
|
|
1006
|
+
const result = sync(deps)(ranges, output, ownerIdBytes);
|
|
1010
1007
|
|
|
1011
1008
|
// Client sync error (handled via Storage) or no changes.
|
|
1012
|
-
if (!
|
|
1009
|
+
if (!result.ok || !result.value) {
|
|
1013
1010
|
return ok({ type: "no-response" });
|
|
1014
1011
|
}
|
|
1015
1012
|
|
|
@@ -1126,14 +1123,11 @@ export const applyProtocolMessageAsRelay =
|
|
|
1126
1123
|
});
|
|
1127
1124
|
}
|
|
1128
1125
|
|
|
1129
|
-
const
|
|
1130
|
-
ownerIdBytes,
|
|
1131
|
-
messages,
|
|
1132
|
-
);
|
|
1126
|
+
const result = await deps.storage.writeMessages(ownerIdBytes, messages);
|
|
1133
1127
|
|
|
1134
|
-
if (!
|
|
1128
|
+
if (!result.ok) {
|
|
1135
1129
|
const errorCode =
|
|
1136
|
-
|
|
1130
|
+
result.error.type === "StorageWriteError"
|
|
1137
1131
|
? ProtocolErrorCode.WriteError
|
|
1138
1132
|
: ProtocolErrorCode.QuotaError;
|
|
1139
1133
|
const message = createProtocolMessageBuffer(ownerId, {
|
|
@@ -1189,13 +1183,13 @@ export const applyProtocolMessageAsRelay =
|
|
|
1189
1183
|
return ok({ type: "response", message: output.unwrap() });
|
|
1190
1184
|
}
|
|
1191
1185
|
|
|
1192
|
-
const
|
|
1186
|
+
const result = sync(deps)(ranges, output, ownerIdBytes);
|
|
1193
1187
|
|
|
1194
|
-
const message =
|
|
1188
|
+
const message = result.ok
|
|
1195
1189
|
? output.unwrap()
|
|
1196
1190
|
: createProtocolMessageBuffer(ownerId, {
|
|
1197
1191
|
messageType: MessageType.Response,
|
|
1198
|
-
errorCode:
|
|
1192
|
+
errorCode: result.error,
|
|
1199
1193
|
}).unwrap();
|
|
1200
1194
|
|
|
1201
1195
|
// Non-initiators always respond to provide sync completion feedback,
|