@evolu/common 7.2.3 → 7.4.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 (92) hide show
  1. package/dist/src/Buffer.d.ts +2 -1
  2. package/dist/src/Buffer.d.ts.map +1 -1
  3. package/dist/src/Buffer.js +1 -0
  4. package/dist/src/Callbacks.d.ts +1 -1
  5. package/dist/src/Callbacks.d.ts.map +1 -1
  6. package/dist/src/Callbacks.js +1 -1
  7. package/dist/src/Console.d.ts +1 -9
  8. package/dist/src/Console.d.ts.map +1 -1
  9. package/dist/src/Console.js +1 -41
  10. package/dist/src/Function.d.ts +61 -0
  11. package/dist/src/Function.d.ts.map +1 -1
  12. package/dist/src/Function.js +19 -0
  13. package/dist/src/Object.d.ts +16 -0
  14. package/dist/src/Object.d.ts.map +1 -1
  15. package/dist/src/Object.js +16 -0
  16. package/dist/src/Redacted.d.ts +89 -0
  17. package/dist/src/Redacted.d.ts.map +1 -0
  18. package/dist/src/Redacted.js +48 -0
  19. package/dist/src/Result.d.ts +36 -29
  20. package/dist/src/Result.d.ts.map +1 -1
  21. package/dist/src/Sqlite.d.ts +9 -7
  22. package/dist/src/Sqlite.d.ts.map +1 -1
  23. package/dist/src/Sqlite.js +8 -0
  24. package/dist/src/Task.d.ts.map +1 -1
  25. package/dist/src/Task.js +6 -4
  26. package/dist/src/Type.d.ts +34 -10
  27. package/dist/src/Type.d.ts.map +1 -1
  28. package/dist/src/Type.js +82 -202
  29. package/dist/src/index.d.ts +1 -0
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/index.js +1 -0
  32. package/dist/src/local-first/Db.d.ts +1 -5
  33. package/dist/src/local-first/Db.d.ts.map +1 -1
  34. package/dist/src/local-first/Db.js +109 -108
  35. package/dist/src/local-first/Evolu.d.ts +3 -0
  36. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  37. package/dist/src/local-first/Evolu.js +6 -7
  38. package/dist/src/local-first/LocalAuth.d.ts +2 -2
  39. package/dist/src/local-first/Owner.d.ts +7 -0
  40. package/dist/src/local-first/Owner.d.ts.map +1 -1
  41. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  42. package/dist/src/local-first/Protocol.js +10 -10
  43. package/dist/src/local-first/Public.d.ts +6 -1
  44. package/dist/src/local-first/Public.d.ts.map +1 -1
  45. package/dist/src/local-first/Public.js +6 -1
  46. package/dist/src/local-first/PublicKysely.js +1 -1
  47. package/dist/src/local-first/Query.d.ts +54 -6
  48. package/dist/src/local-first/Query.d.ts.map +1 -1
  49. package/dist/src/local-first/Query.js +130 -11
  50. package/dist/src/local-first/Relay.d.ts.map +1 -1
  51. package/dist/src/local-first/Relay.js +17 -18
  52. package/dist/src/local-first/Schema.d.ts +15 -14
  53. package/dist/src/local-first/Schema.d.ts.map +1 -1
  54. package/dist/src/local-first/Schema.js +43 -51
  55. package/dist/src/local-first/Storage.d.ts +2 -2
  56. package/dist/src/local-first/Storage.d.ts.map +1 -1
  57. package/dist/src/local-first/Storage.js +2 -6
  58. package/dist/src/local-first/Sync.d.ts +9 -4
  59. package/dist/src/local-first/Sync.d.ts.map +1 -1
  60. package/dist/src/local-first/Sync.js +132 -59
  61. package/dist/src/local-first/index.d.ts +9 -2
  62. package/dist/src/local-first/index.d.ts.map +1 -1
  63. package/dist/src/local-first/index.js +9 -2
  64. package/package.json +3 -4
  65. package/src/Buffer.ts +2 -1
  66. package/src/Callbacks.ts +1 -1
  67. package/src/Console.ts +1 -10
  68. package/src/Function.ts +74 -0
  69. package/src/Object.ts +20 -0
  70. package/src/Redacted.ts +118 -0
  71. package/src/Result.ts +38 -29
  72. package/src/Sqlite.ts +18 -7
  73. package/src/Task.ts +6 -4
  74. package/src/Type.ts +148 -20
  75. package/src/index.ts +1 -0
  76. package/src/local-first/Db.ts +181 -209
  77. package/src/local-first/Evolu.ts +9 -6
  78. package/src/local-first/LocalAuth.ts +2 -2
  79. package/src/local-first/Owner.ts +8 -0
  80. package/src/local-first/Protocol.ts +10 -16
  81. package/src/local-first/Public.ts +6 -1
  82. package/src/local-first/PublicKysely.ts +1 -1
  83. package/src/local-first/Query.ts +216 -21
  84. package/src/local-first/Relay.ts +20 -24
  85. package/src/local-first/Schema.ts +83 -70
  86. package/src/local-first/Storage.ts +3 -8
  87. package/src/local-first/Sync.ts +190 -66
  88. package/src/local-first/index.ts +9 -2
  89. package/dist/src/local-first/Diff.d.ts +0 -43
  90. package/dist/src/local-first/Diff.d.ts.map +0 -1
  91. package/dist/src/local-first/Diff.js +0 -97
  92. 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 =>
@@ -337,146 +334,141 @@ export const createDbWorkerForPlatform = (
337
334
  DbWorkerOutput,
338
335
  DbWorkerDeps
339
336
  >({
340
- init: createInit(platformDeps),
341
- handlers,
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
- let appOwner: AppOwner;
370
- let clock: Clock;
371
-
372
- const dbIsInitialized = currentDbSchema.value.tables.some(
373
- (table) => table.name === "evolu_version",
340
+ const deps = await createDbWorkerDeps(
341
+ platformDeps,
342
+ initMessage,
343
+ postMessage,
374
344
  );
375
345
 
376
- if (dbIsInitialized) {
377
- const currentVersion = sqlite.exec<{
378
- protocolVersion: number;
379
- }>(sql`select protocolVersion from evolu_version limit 1;`);
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
- appOwner = {
403
- type: "AppOwner",
404
- id: config.appOwnerId,
405
- encryptionKey: config.appOwnerEncryptionKey,
406
- writeKey: config.appOwnerWriteKey,
407
- mnemonic: config.appOwnerMnemonic,
408
- };
351
+ return deps.value;
352
+ },
353
+ handlers,
354
+ });
409
355
 
410
- clock = createClock({ ...platformDeps, sqlite })(
411
- timestampBytesToTimestamp(config.clock),
412
- );
413
- } else {
414
- appOwner =
415
- initMessage.config.externalAppOwner ??
416
- createAppOwner(createOwnerSecret(platformDeps));
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
- clock = createClock({ ...platformDeps, sqlite })();
413
+ clock = createClock(deps)(timestampBytesToTimestamp(config.clock));
414
+ } else {
415
+ appOwner =
416
+ initMessage.config.externalAppOwner ??
417
+ createAppOwner(createOwnerSecret(platformDeps));
419
418
 
420
- const result = initializeDb({ sqlite })(appOwner, clock.get());
421
- if (!result.ok) return result;
422
- }
419
+ clock = createClock(deps)();
423
420
 
424
- const result = ensureDbSchema({ sqlite })(
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
- const sync = createSync({
431
- ...platformDeps,
432
- clock,
433
- sqlite,
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
- const deps: DbWorkerDeps = {
462
- ...platformDeps,
463
- getQueryRowsCache,
464
- postMessage,
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
- 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
+ },
471
450
  });
451
+ if (!sync.ok) return sync;
472
452
 
473
- if (!deps.ok) {
474
- postMessage({ type: "onError", error: deps.error });
475
- return null;
453
+ {
454
+ const result = tryApplyQuarantinedMessages({
455
+ ...deps,
456
+ dbSchema: initMessage.dbSchema,
457
+ })();
458
+ if (!result.ok) return result;
476
459
  }
477
460
 
478
- return deps.value;
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 resetResult = deps.sqlite.transaction(() => {
676
+ const result = deps.sqlite.transaction(() => {
651
677
  const dbSchema = getDbSchema(deps)();
652
678
  if (!dbSchema.ok) return dbSchema;
653
679
 
654
- for (const table of dbSchema.value.tables) {
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(table.name)};
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 dbSchema = getDbSchema(deps)();
669
- if (!dbSchema.ok) return dbSchema;
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
- const initializeDbResult = initializeDb(deps)(appOwner, clock.get());
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 (!resetResult.ok) {
689
- deps.postMessage({ type: "onError", error: resetResult.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 ensureSchema = deps.sqlite.transaction(() => {
702
- const dbSchema = getDbSchema(deps)();
703
- if (!dbSchema.ok) return dbSchema;
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 (!ensureSchema.ok) {
715
- deps.postMessage({ type: "onError", error: ensureSchema.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
- };
@@ -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 previousResult: unknown = null;
842
+ let previousRows: unknown = null;
840
843
  const unsubscribe = subscribedQueries.subscribe(query)(() => {
841
- const result = evolu.getQueryRows(query);
842
- if (previousResult === result) return;
843
- previousResult = result;
844
+ const rows = evolu.getQueryRows(query);
845
+ if (previousRows === rows) return;
846
+ previousRows = rows;
844
847
  listener();
845
848
  });
846
849
  return () => {
847
- previousResult = null;
850
+ previousRows = null;
848
851
  unsubscribe();
849
852
  };
850
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: (
@@ -28,6 +28,7 @@ import { TimestampBytes } from "./Timestamp.js";
28
28
  */
29
29
  export interface ReadonlyOwner {
30
30
  readonly id: OwnerId;
31
+ /** TODO: Wrap with `Redacted` in the next major version. */
31
32
  readonly encryptionKey: OwnerEncryptionKey;
32
33
  }
33
34
 
@@ -63,6 +64,7 @@ export interface ReadonlyOwner {
63
64
  * @see {@link createSharedReadonlyOwner}
64
65
  */
65
66
  export interface Owner extends ReadonlyOwner {
67
+ /** TODO: Wrap with `Redacted` in the next major version. */
66
68
  readonly writeKey: OwnerWriteKey;
67
69
  }
68
70
 
@@ -184,10 +186,16 @@ export interface AppOwner extends Owner {
184
186
  * The mnemonic that was used to derive the AppOwner keys. Optional when the
185
187
  * AppOwner is created from external keys to avoid sharing the mnemonic with
186
188
  * the Evolu app.
189
+ *
190
+ * TODO: Wrap with `Redacted` in the next major version.
187
191
  */
188
192
  readonly mnemonic?: Mnemonic | null;
189
193
  }
190
194
 
195
+ export interface AppOwnerDep {
196
+ readonly appOwner: AppOwner;
197
+ }
198
+
191
199
  /** Creates an {@link AppOwner} from an {@link OwnerSecret}. */
192
200
  export const createAppOwner = (secret: OwnerSecret): AppOwner => ({
193
201
  ...createOwner(secret),
@@ -972,12 +972,9 @@ export const applyProtocolMessageAsClient =
972
972
  const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
973
973
 
974
974
  if (isNonEmptyReadonlyArray(messages)) {
975
- const writeResult = await deps.storage.writeMessages(
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 (!writeResult.ok) return ok({ type: "no-response" });
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 syncResult = sync(deps)(ranges, output, ownerIdBytes);
1006
+ const result = sync(deps)(ranges, output, ownerIdBytes);
1010
1007
 
1011
1008
  // Client sync error (handled via Storage) or no changes.
1012
- if (!syncResult.ok || !syncResult.value) {
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 writeResult = await deps.storage.writeMessages(
1130
- ownerIdBytes,
1131
- messages,
1132
- );
1126
+ const result = await deps.storage.writeMessages(ownerIdBytes, messages);
1133
1127
 
1134
- if (!writeResult.ok) {
1128
+ if (!result.ok) {
1135
1129
  const errorCode =
1136
- writeResult.error.type === "StorageWriteError"
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 syncResult = sync(deps)(ranges, output, ownerIdBytes);
1186
+ const result = sync(deps)(ranges, output, ownerIdBytes);
1193
1187
 
1194
- const message = syncResult.ok
1188
+ const message = result.ok
1195
1189
  ? output.unwrap()
1196
1190
  : createProtocolMessageBuffer(ownerId, {
1197
1191
  messageType: MessageType.Response,
1198
- errorCode: syncResult.error,
1192
+ errorCode: result.error,
1199
1193
  }).unwrap();
1200
1194
 
1201
1195
  // Non-initiators always respond to provide sync completion feedback,
@@ -1,5 +1,10 @@
1
1
  /**
2
- * Local-first code to be imported from "@evolu/common"
2
+ * Public local-first API exported from "@evolu/common"
3
+ *
4
+ * This module provides the supported consumer-facing API for Evolu's
5
+ * local-first system: prefer importing from `@evolu/common` (this module) when
6
+ * using Evolu in applications. If you require access to lower level internals,
7
+ * consider importing from `@evolu/common/local-first`.
3
8
  *
4
9
  * @module
5
10
  */
@@ -13,7 +13,7 @@ import {
13
13
  TableNode,
14
14
  ValueNode,
15
15
  } from "kysely";
16
- import { kyselyJsonIdentifier } from "./Diff.js";
16
+ import { kyselyJsonIdentifier } from "./Query.js";
17
17
 
18
18
  export { sql } from "kysely";
19
19
  export type { NotNull } from "kysely";