@evolu/common 7.2.2 → 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 +32 -1
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +32 -1
- package/dist/src/Order.d.ts +2 -1
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +1 -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 +112 -117
- 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 +14 -12
- 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 +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +12 -12
- 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 +10 -7
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +154 -103
- package/dist/src/local-first/Timestamp.d.ts +13 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +14 -1
- 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 +40 -1
- package/src/Order.ts +2 -1
- 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 +182 -217
- package/src/local-first/Evolu.ts +21 -10
- package/src/local-first/LocalAuth.ts +2 -2
- package/src/local-first/Owner.ts +4 -0
- package/src/local-first/Protocol.ts +13 -19
- 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 +215 -135
- package/src/local-first/Timestamp.ts +14 -1
- 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 =>
|
|
@@ -336,154 +333,142 @@ export const createDbWorkerForPlatform = (
|
|
|
336
333
|
DbWorkerInput,
|
|
337
334
|
DbWorkerOutput,
|
|
338
335
|
DbWorkerDeps
|
|
339
|
-
>({
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
(platformDeps: DbWorkerPlatformDeps) =>
|
|
343
|
-
async (
|
|
344
|
-
initMessage: Extract<DbWorkerInput, { type: "init" }>,
|
|
345
|
-
postMessage: (msg: DbWorkerOutput) => void,
|
|
346
|
-
): Promise<DbWorkerDeps | null> => {
|
|
347
|
-
platformDeps.console.enabled = initMessage.config.enableLogging ?? false;
|
|
348
|
-
|
|
349
|
-
const sqliteResult = await createSqlite(platformDeps)(
|
|
350
|
-
initMessage.config.name,
|
|
351
|
-
{
|
|
352
|
-
memory: initMessage.config.inMemory ?? false,
|
|
353
|
-
encryptionKey: initMessage.config.encryptionKey ?? undefined,
|
|
354
|
-
},
|
|
355
|
-
);
|
|
356
|
-
if (!sqliteResult.ok) {
|
|
357
|
-
postMessage({ type: "onError", error: sqliteResult.error });
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
const sqlite = sqliteResult.value;
|
|
361
|
-
|
|
362
|
-
const deps = sqlite.transaction(() => {
|
|
363
|
-
const currentDbSchema = getDbSchema({ sqlite })();
|
|
364
|
-
if (!currentDbSchema.ok) return currentDbSchema;
|
|
336
|
+
>({
|
|
337
|
+
init: async (initMessage, postMessage) => {
|
|
338
|
+
platformDeps.console.enabled = initMessage.config.enableLogging ?? false;
|
|
365
339
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
(table) => table.name === "evolu_version",
|
|
340
|
+
const deps = await createDbWorkerDeps(
|
|
341
|
+
platformDeps,
|
|
342
|
+
initMessage,
|
|
343
|
+
postMessage,
|
|
371
344
|
);
|
|
372
345
|
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!currentVersion.ok) return currentVersion;
|
|
378
|
-
|
|
379
|
-
// TODO: Handle version migrations here if needed
|
|
380
|
-
// const [{ protocolVersion }] = protocolVersionResult.value.rows;
|
|
381
|
-
// if (protocolVersion < currentProtocolVersion) {
|
|
382
|
-
// const migrateResult = migrateDatabase({ sqlite })(
|
|
383
|
-
// protocolVersion,
|
|
384
|
-
// currentProtocolVersion
|
|
385
|
-
// );
|
|
386
|
-
// if (!migrateResult.ok) return migrateResult;
|
|
387
|
-
// }
|
|
388
|
-
|
|
389
|
-
const configResult = sqlite.exec<{
|
|
390
|
-
clock: TimestampBytes;
|
|
391
|
-
appOwnerId: OwnerId;
|
|
392
|
-
appOwnerEncryptionKey: OwnerEncryptionKey;
|
|
393
|
-
appOwnerWriteKey: OwnerWriteKey;
|
|
394
|
-
appOwnerMnemonic: Mnemonic | null;
|
|
395
|
-
}>(sql`
|
|
396
|
-
select
|
|
397
|
-
clock,
|
|
398
|
-
appOwnerId,
|
|
399
|
-
appOwnerEncryptionKey,
|
|
400
|
-
appOwnerWriteKey,
|
|
401
|
-
appOwnerMnemonic
|
|
402
|
-
from evolu_config
|
|
403
|
-
limit 1;
|
|
404
|
-
`);
|
|
405
|
-
if (!configResult.ok) return configResult;
|
|
346
|
+
if (!deps.ok) {
|
|
347
|
+
postMessage({ type: "onError", error: deps.error });
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
406
350
|
|
|
407
|
-
|
|
351
|
+
return deps.value;
|
|
352
|
+
},
|
|
353
|
+
handlers,
|
|
354
|
+
});
|
|
408
355
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
+
};
|
|
416
412
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
initMessage.config.externalAppOwner ??
|
|
423
|
-
createAppOwner(createOwnerSecret(platformDeps));
|
|
413
|
+
clock = createClock(deps)(timestampBytesToTimestamp(config.clock));
|
|
414
|
+
} else {
|
|
415
|
+
appOwner =
|
|
416
|
+
initMessage.config.externalAppOwner ??
|
|
417
|
+
createAppOwner(createOwnerSecret(platformDeps));
|
|
424
418
|
|
|
425
|
-
|
|
419
|
+
clock = createClock(deps)();
|
|
426
420
|
|
|
427
|
-
|
|
428
|
-
if (!result.ok) return result;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const result = ensureDbSchema({ sqlite })(
|
|
432
|
-
initMessage.dbSchema,
|
|
433
|
-
currentDbSchema.value,
|
|
434
|
-
);
|
|
421
|
+
const result = initializeDb(deps)(appOwner, clock.get());
|
|
435
422
|
if (!result.ok) return result;
|
|
423
|
+
}
|
|
436
424
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
symmetricCrypto: createSymmetricCrypto(platformDeps),
|
|
442
|
-
timestampConfig: initMessage.config,
|
|
443
|
-
postMessage,
|
|
444
|
-
})({
|
|
445
|
-
appOwner,
|
|
446
|
-
transports: initMessage.config.transports,
|
|
447
|
-
onError: (error) => {
|
|
448
|
-
postMessage({ type: "onError", error });
|
|
449
|
-
},
|
|
450
|
-
onReceive: () => {
|
|
451
|
-
postMessage({ type: "refreshQueries" });
|
|
452
|
-
},
|
|
453
|
-
});
|
|
454
|
-
if (!sync.ok) return sync;
|
|
455
|
-
|
|
456
|
-
sync.value.useOwner(true, appOwner);
|
|
457
|
-
|
|
458
|
-
const tabQueryRowsCacheMap = new Map<Id, QueryRowsCache>();
|
|
459
|
-
const getQueryRowsCache = (tabId: Id) => {
|
|
460
|
-
let cache = tabQueryRowsCacheMap.get(tabId);
|
|
461
|
-
if (!cache) {
|
|
462
|
-
cache = createQueryRowsCache();
|
|
463
|
-
tabQueryRowsCacheMap.set(tabId, cache);
|
|
464
|
-
}
|
|
465
|
-
return cache;
|
|
466
|
-
};
|
|
425
|
+
{
|
|
426
|
+
const result = ensureDbSchema(deps)(initMessage.dbSchema, dbSchema.value);
|
|
427
|
+
if (!result.ok) return result;
|
|
428
|
+
}
|
|
467
429
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
sqlite,
|
|
473
|
-
sync: sync.value,
|
|
474
|
-
appOwner,
|
|
475
|
-
};
|
|
430
|
+
{
|
|
431
|
+
const result = ensureMessageQuarantineTable(deps);
|
|
432
|
+
if (!result.ok) return result;
|
|
433
|
+
}
|
|
476
434
|
|
|
477
|
-
|
|
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
|
+
},
|
|
478
450
|
});
|
|
451
|
+
if (!sync.ok) return sync;
|
|
479
452
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
453
|
+
{
|
|
454
|
+
const result = tryApplyQuarantinedMessages({
|
|
455
|
+
...deps,
|
|
456
|
+
dbSchema: initMessage.dbSchema,
|
|
457
|
+
})();
|
|
458
|
+
if (!result.ok) return result;
|
|
483
459
|
}
|
|
484
460
|
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
};
|
|
487
472
|
|
|
488
473
|
const initializeDb =
|
|
489
474
|
(deps: SqliteDep) =>
|
|
@@ -555,7 +540,6 @@ const initializeDb =
|
|
|
555
540
|
`,
|
|
556
541
|
|
|
557
542
|
// Index for reading database changes by owner and timestamp.
|
|
558
|
-
// Timestamp always corresponds to a DbChange.
|
|
559
543
|
sql`
|
|
560
544
|
create index evolu_history_ownerId_timestamp on evolu_history (
|
|
561
545
|
"ownerId",
|
|
@@ -583,6 +567,41 @@ const initializeDb =
|
|
|
583
567
|
return ok();
|
|
584
568
|
};
|
|
585
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
|
+
|
|
586
605
|
const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
587
606
|
getAppOwner: (deps) => () => {
|
|
588
607
|
deps.postMessage({
|
|
@@ -654,11 +673,11 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
654
673
|
},
|
|
655
674
|
|
|
656
675
|
reset: (deps) => (message) => {
|
|
657
|
-
const
|
|
676
|
+
const result = deps.sqlite.transaction(() => {
|
|
658
677
|
const dbSchema = getDbSchema(deps)();
|
|
659
678
|
if (!dbSchema.ok) return dbSchema;
|
|
660
679
|
|
|
661
|
-
for (const
|
|
680
|
+
for (const tableName in dbSchema.value.tables) {
|
|
662
681
|
/**
|
|
663
682
|
* The dropped table is completely removed from the database schema and
|
|
664
683
|
* the disk file. The table can not be recovered. All indices and
|
|
@@ -666,34 +685,27 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
666
685
|
* https://sqlite.org/lang_droptable.html
|
|
667
686
|
*/
|
|
668
687
|
const result = deps.sqlite.exec(sql`
|
|
669
|
-
drop table ${sql.identifier(
|
|
688
|
+
drop table ${sql.identifier(tableName)};
|
|
670
689
|
`);
|
|
671
690
|
if (!result.ok) return result;
|
|
672
691
|
}
|
|
673
692
|
|
|
674
693
|
if (message.restore) {
|
|
675
|
-
const
|
|
676
|
-
if (!
|
|
677
|
-
|
|
678
|
-
const ensureDbSchemaResult = ensureDbSchema(deps)(
|
|
679
|
-
message.restore.dbSchema,
|
|
680
|
-
dbSchema.value,
|
|
681
|
-
);
|
|
682
|
-
if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
694
|
+
const result = ensureDbSchema(deps)(message.restore.dbSchema);
|
|
695
|
+
if (!result.ok) return result;
|
|
683
696
|
|
|
684
697
|
const secret = mnemonicToOwnerSecret(message.restore.mnemonic);
|
|
685
698
|
const appOwner = createAppOwner(secret);
|
|
686
699
|
const clock = createClock(deps)();
|
|
687
700
|
|
|
688
|
-
|
|
689
|
-
if (!initializeDbResult.ok) return initializeDbResult;
|
|
701
|
+
return initializeDb(deps)(appOwner, clock.get());
|
|
690
702
|
}
|
|
691
703
|
|
|
692
704
|
return ok();
|
|
693
705
|
});
|
|
694
706
|
|
|
695
|
-
if (!
|
|
696
|
-
deps.postMessage({ type: "onError", error:
|
|
707
|
+
if (!result.ok) {
|
|
708
|
+
deps.postMessage({ type: "onError", error: result.error });
|
|
697
709
|
return;
|
|
698
710
|
}
|
|
699
711
|
|
|
@@ -705,21 +717,12 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
705
717
|
},
|
|
706
718
|
|
|
707
719
|
ensureDbSchema: (deps) => (message) => {
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const ensureDbSchemaResult = ensureDbSchema(deps)(
|
|
713
|
-
message.dbSchema,
|
|
714
|
-
dbSchema.value,
|
|
715
|
-
);
|
|
716
|
-
if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
717
|
-
|
|
718
|
-
return ok();
|
|
719
|
-
});
|
|
720
|
+
const result = deps.sqlite.transaction(() =>
|
|
721
|
+
ensureDbSchema(deps)(message.dbSchema),
|
|
722
|
+
);
|
|
720
723
|
|
|
721
|
-
if (!
|
|
722
|
-
deps.postMessage({ type: "onError", error:
|
|
724
|
+
if (!result.ok) {
|
|
725
|
+
deps.postMessage({ type: "onError", error: result.error });
|
|
723
726
|
return;
|
|
724
727
|
}
|
|
725
728
|
},
|
|
@@ -743,41 +746,3 @@ const handlers: Omit<MessageHandlers<DbWorkerInput, DbWorkerDeps>, "init"> = {
|
|
|
743
746
|
deps.sync.useOwner(message.use, message.owner);
|
|
744
747
|
},
|
|
745
748
|
};
|
|
746
|
-
|
|
747
|
-
const loadQueries =
|
|
748
|
-
(deps: GetQueryRowsCacheDep & SqliteDep) =>
|
|
749
|
-
(
|
|
750
|
-
tabId: Id,
|
|
751
|
-
queries: ReadonlyArray<Query>,
|
|
752
|
-
): Result<ReadonlyArray<QueryPatches>, SqliteError> => {
|
|
753
|
-
const queriesRows = [];
|
|
754
|
-
|
|
755
|
-
for (const query of queries) {
|
|
756
|
-
const sqlQuery = deserializeQuery(query);
|
|
757
|
-
const result = deps.sqlite.exec(sqlQuery);
|
|
758
|
-
if (!result.ok) return result;
|
|
759
|
-
|
|
760
|
-
queriesRows.push([query, result.value.rows] as const);
|
|
761
|
-
if (sqlQuery.options?.logExplainQueryPlan) {
|
|
762
|
-
explainSqliteQueryPlan(deps)(sqlQuery);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const queryRowsCache = deps.getQueryRowsCache(tabId);
|
|
767
|
-
|
|
768
|
-
const previousState = queryRowsCache.get();
|
|
769
|
-
queryRowsCache.set(queriesRows);
|
|
770
|
-
|
|
771
|
-
const currentState = queryRowsCache.get();
|
|
772
|
-
|
|
773
|
-
const queryPatchesArray = queries.map(
|
|
774
|
-
(query): QueryPatches => ({
|
|
775
|
-
query,
|
|
776
|
-
patches: makePatches(
|
|
777
|
-
previousState.get(query),
|
|
778
|
-
currentState.get(query) ?? emptyRows,
|
|
779
|
-
),
|
|
780
|
-
}),
|
|
781
|
-
);
|
|
782
|
-
return ok(queryPatchesArray);
|
|
783
|
-
};
|
package/src/local-first/Evolu.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
isNonEmptyArray,
|
|
5
5
|
isNonEmptyReadonlyArray,
|
|
6
6
|
} from "../Array.js";
|
|
7
|
-
import { assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
7
|
+
import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
8
8
|
import { createCallbacks } from "../Callbacks.js";
|
|
9
9
|
import { ConsoleDep } from "../Console.js";
|
|
10
10
|
import { RandomBytesDep, SymmetricCryptoDecryptError } from "../Crypto.js";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isSqlMutation,
|
|
18
18
|
SafeSql,
|
|
19
19
|
SqliteBoolean,
|
|
20
|
+
sqliteBooleanToBoolean,
|
|
20
21
|
SqliteError,
|
|
21
22
|
SqliteQuery,
|
|
22
23
|
} from "../Sqlite.js";
|
|
@@ -36,11 +37,11 @@ import {
|
|
|
36
37
|
} from "../Type.js";
|
|
37
38
|
import { IntentionalNever } from "../Types.js";
|
|
38
39
|
import { CreateDbWorkerDep, DbConfig, defaultDbConfig } from "./Db.js";
|
|
39
|
-
import { applyPatches } from "./Diff.js";
|
|
40
40
|
import { AppOwner } from "./Owner.js";
|
|
41
41
|
import { FlushSyncDep, ReloadAppDep } from "./Platform.js";
|
|
42
42
|
import { ProtocolError } from "./Protocol.js";
|
|
43
43
|
import {
|
|
44
|
+
applyPatches,
|
|
44
45
|
createSubscribedQueries,
|
|
45
46
|
emptyRows,
|
|
46
47
|
Queries,
|
|
@@ -100,6 +101,9 @@ export interface EvoluConfig extends Partial<DbConfig> {
|
|
|
100
101
|
* URL to reload browser tabs after reset or restore.
|
|
101
102
|
*
|
|
102
103
|
* The default value is `/`.
|
|
104
|
+
*
|
|
105
|
+
* Note: This option will be moved to web platform deps in the next major
|
|
106
|
+
* version.
|
|
103
107
|
*/
|
|
104
108
|
readonly reloadUrl?: string;
|
|
105
109
|
}
|
|
@@ -730,13 +734,20 @@ const createEvoluInstance =
|
|
|
730
734
|
} else {
|
|
731
735
|
const { id: _, isDeleted, ...values } = result.value;
|
|
732
736
|
|
|
733
|
-
const dbChange =
|
|
737
|
+
const dbChange = {
|
|
734
738
|
table,
|
|
735
739
|
id,
|
|
736
740
|
values,
|
|
737
741
|
isInsert: kind === "insert" || kind === "upsert",
|
|
738
|
-
isDelete: SqliteBoolean.is(isDeleted)
|
|
739
|
-
|
|
742
|
+
isDelete: SqliteBoolean.is(isDeleted)
|
|
743
|
+
? sqliteBooleanToBoolean(isDeleted)
|
|
744
|
+
: null,
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
assert(
|
|
748
|
+
DbChange.is(dbChange),
|
|
749
|
+
`Invalid DbChange for table '${table}': Please check schema type errors.`,
|
|
750
|
+
);
|
|
740
751
|
|
|
741
752
|
mutateMicrotaskQueue.push([
|
|
742
753
|
{ ...dbChange, ownerId: options?.ownerId },
|
|
@@ -828,15 +839,15 @@ const createEvoluInstance =
|
|
|
828
839
|
|
|
829
840
|
subscribeQuery: (query) => (listener) => {
|
|
830
841
|
// Call the listener only if the result has been changed.
|
|
831
|
-
let
|
|
842
|
+
let previousRows: unknown = null;
|
|
832
843
|
const unsubscribe = subscribedQueries.subscribe(query)(() => {
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
835
|
-
|
|
844
|
+
const rows = evolu.getQueryRows(query);
|
|
845
|
+
if (previousRows === rows) return;
|
|
846
|
+
previousRows = rows;
|
|
836
847
|
listener();
|
|
837
848
|
});
|
|
838
849
|
return () => {
|
|
839
|
-
|
|
850
|
+
previousRows = null;
|
|
840
851
|
unsubscribe();
|
|
841
852
|
};
|
|
842
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),
|