@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.
Files changed (77) hide show
  1. package/dist/src/Function.d.ts +61 -0
  2. package/dist/src/Function.d.ts.map +1 -1
  3. package/dist/src/Function.js +19 -0
  4. package/dist/src/Object.d.ts +32 -1
  5. package/dist/src/Object.d.ts.map +1 -1
  6. package/dist/src/Object.js +32 -1
  7. package/dist/src/Order.d.ts +2 -1
  8. package/dist/src/Order.d.ts.map +1 -1
  9. package/dist/src/Order.js +1 -0
  10. package/dist/src/Result.d.ts +36 -29
  11. package/dist/src/Result.d.ts.map +1 -1
  12. package/dist/src/Sqlite.d.ts +9 -7
  13. package/dist/src/Sqlite.d.ts.map +1 -1
  14. package/dist/src/Sqlite.js +8 -0
  15. package/dist/src/Task.d.ts.map +1 -1
  16. package/dist/src/Task.js +6 -4
  17. package/dist/src/Type.d.ts +30 -2
  18. package/dist/src/Type.d.ts.map +1 -1
  19. package/dist/src/Type.js +82 -0
  20. package/dist/src/local-first/Db.d.ts +1 -5
  21. package/dist/src/local-first/Db.d.ts.map +1 -1
  22. package/dist/src/local-first/Db.js +112 -117
  23. package/dist/src/local-first/Evolu.d.ts +3 -0
  24. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  25. package/dist/src/local-first/Evolu.js +14 -12
  26. package/dist/src/local-first/LocalAuth.d.ts +2 -2
  27. package/dist/src/local-first/Owner.d.ts +3 -0
  28. package/dist/src/local-first/Owner.d.ts.map +1 -1
  29. package/dist/src/local-first/Protocol.d.ts +1 -1
  30. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  31. package/dist/src/local-first/Protocol.js +12 -12
  32. package/dist/src/local-first/PublicKysely.js +1 -1
  33. package/dist/src/local-first/Query.d.ts +54 -6
  34. package/dist/src/local-first/Query.d.ts.map +1 -1
  35. package/dist/src/local-first/Query.js +130 -11
  36. package/dist/src/local-first/Relay.d.ts.map +1 -1
  37. package/dist/src/local-first/Relay.js +17 -18
  38. package/dist/src/local-first/Schema.d.ts +15 -14
  39. package/dist/src/local-first/Schema.d.ts.map +1 -1
  40. package/dist/src/local-first/Schema.js +43 -51
  41. package/dist/src/local-first/Storage.d.ts +2 -2
  42. package/dist/src/local-first/Storage.d.ts.map +1 -1
  43. package/dist/src/local-first/Storage.js +2 -6
  44. package/dist/src/local-first/Sync.d.ts +10 -7
  45. package/dist/src/local-first/Sync.d.ts.map +1 -1
  46. package/dist/src/local-first/Sync.js +154 -103
  47. package/dist/src/local-first/Timestamp.d.ts +13 -0
  48. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  49. package/dist/src/local-first/Timestamp.js +14 -1
  50. package/dist/src/local-first/index.d.ts +0 -1
  51. package/dist/src/local-first/index.d.ts.map +1 -1
  52. package/dist/src/local-first/index.js +0 -1
  53. package/package.json +1 -1
  54. package/src/Function.ts +74 -0
  55. package/src/Object.ts +40 -1
  56. package/src/Order.ts +2 -1
  57. package/src/Result.ts +38 -29
  58. package/src/Sqlite.ts +18 -7
  59. package/src/Task.ts +6 -4
  60. package/src/Type.ts +134 -1
  61. package/src/local-first/Db.ts +182 -217
  62. package/src/local-first/Evolu.ts +21 -10
  63. package/src/local-first/LocalAuth.ts +2 -2
  64. package/src/local-first/Owner.ts +4 -0
  65. package/src/local-first/Protocol.ts +13 -19
  66. package/src/local-first/PublicKysely.ts +1 -1
  67. package/src/local-first/Query.ts +216 -21
  68. package/src/local-first/Relay.ts +20 -24
  69. package/src/local-first/Schema.ts +83 -70
  70. package/src/local-first/Storage.ts +3 -8
  71. package/src/local-first/Sync.ts +215 -135
  72. package/src/local-first/Timestamp.ts +14 -1
  73. package/src/local-first/index.ts +0 -1
  74. package/dist/src/local-first/Diff.d.ts +0 -43
  75. package/dist/src/local-first/Diff.d.ts.map +0 -1
  76. package/dist/src/local-first/Diff.js +0 -97
  77. package/src/local-first/Diff.ts +0 -144
@@ -1,4 +1,9 @@
1
- import { isNonEmptyArray, NonEmptyReadonlyArray } from "../Array.js";
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
- createQueryRowsCache,
44
- deserializeQuery,
45
- emptyRows,
47
+ createGetQueryRowsCache,
48
+ GetQueryRowsCacheDep,
49
+ loadQueries,
46
50
  Query,
47
- QueryRowsCache,
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
- >({ init: createDbWorkerDeps(platformDeps), handlers });
340
-
341
- const createDbWorkerDeps =
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
- let appOwner: AppOwner;
367
- let clock: Clock;
368
-
369
- const dbIsInitialized = currentDbSchema.value.tables.some(
370
- (table) => table.name === "evolu_version",
340
+ const deps = await createDbWorkerDeps(
341
+ platformDeps,
342
+ initMessage,
343
+ postMessage,
371
344
  );
372
345
 
373
- if (dbIsInitialized) {
374
- const currentVersion = sqlite.exec<{
375
- protocolVersion: number;
376
- }>(sql`select protocolVersion from evolu_version limit 1;`);
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
- const [config] = configResult.value.rows;
351
+ return deps.value;
352
+ },
353
+ handlers,
354
+ });
408
355
 
409
- appOwner = {
410
- type: "AppOwner",
411
- id: config.appOwnerId,
412
- encryptionKey: config.appOwnerEncryptionKey,
413
- writeKey: config.appOwnerWriteKey,
414
- mnemonic: config.appOwnerMnemonic,
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
- clock = createClock({ ...platformDeps, sqlite })(
418
- timestampBytesToTimestamp(config.clock),
419
- );
420
- } else {
421
- appOwner =
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
- clock = createClock({ ...platformDeps, sqlite })();
419
+ clock = createClock(deps)();
426
420
 
427
- const result = initializeDb({ sqlite })(appOwner, clock.get());
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
- const sync = createSync({
438
- ...platformDeps,
439
- clock,
440
- sqlite,
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
- const deps: DbWorkerDeps = {
469
- ...platformDeps,
470
- getQueryRowsCache,
471
- postMessage,
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
- return ok(deps);
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
- if (!deps.ok) {
481
- postMessage({ type: "onError", error: deps.error });
482
- return null;
453
+ {
454
+ const result = tryApplyQuarantinedMessages({
455
+ ...deps,
456
+ dbSchema: initMessage.dbSchema,
457
+ })();
458
+ if (!result.ok) return result;
483
459
  }
484
460
 
485
- return deps.value;
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 resetResult = deps.sqlite.transaction(() => {
676
+ const result = deps.sqlite.transaction(() => {
658
677
  const dbSchema = getDbSchema(deps)();
659
678
  if (!dbSchema.ok) return dbSchema;
660
679
 
661
- for (const table of dbSchema.value.tables) {
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(table.name)};
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 dbSchema = getDbSchema(deps)();
676
- if (!dbSchema.ok) return dbSchema;
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
- const initializeDbResult = initializeDb(deps)(appOwner, clock.get());
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 (!resetResult.ok) {
696
- deps.postMessage({ type: "onError", error: resetResult.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 ensureSchema = deps.sqlite.transaction(() => {
709
- const dbSchema = getDbSchema(deps)();
710
- if (!dbSchema.ok) return dbSchema;
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 (!ensureSchema.ok) {
722
- deps.postMessage({ type: "onError", error: ensureSchema.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
- };
@@ -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 = DbChange.orThrow({
737
+ const dbChange = {
734
738
  table,
735
739
  id,
736
740
  values,
737
741
  isInsert: kind === "insert" || kind === "upsert",
738
- isDelete: SqliteBoolean.is(isDeleted) ? Boolean(isDeleted) : null,
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 previousResult: unknown = null;
842
+ let previousRows: unknown = null;
832
843
  const unsubscribe = subscribedQueries.subscribe(query)(() => {
833
- const result = evolu.getQueryRows(query);
834
- if (previousResult === result) return;
835
- previousResult = result;
844
+ const rows = evolu.getQueryRows(query);
845
+ if (previousRows === rows) return;
846
+ previousRows = rows;
836
847
  listener();
837
848
  });
838
849
  return () => {
839
- previousResult = null;
850
+ previousRows = null;
840
851
  unsubscribe();
841
852
  };
842
853
  },
@@ -47,9 +47,9 @@ export interface LocalAuthDep {
47
47
  }
48
48
 
49
49
  /**
50
- * - **⚠️
51
- *
52
50
  * Secure storage interface that must be implemented by each platform.
51
+ *
52
+ * @experimental
53
53
  */
54
54
  export interface SecureStorage {
55
55
  setItem: (
@@ -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),