@evolu/common 6.0.1-preview.0 → 6.0.1-preview.10
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/Assert.d.ts +6 -3
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -3
- package/dist/src/Crypto.d.ts +11 -0
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Evolu/Config.d.ts +17 -4
- package/dist/src/Evolu/Config.d.ts.map +1 -1
- package/dist/src/Evolu/Config.js +1 -1
- package/dist/src/Evolu/Db.d.ts +21 -13
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +81 -64
- package/dist/src/Evolu/Evolu.d.ts +6 -6
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +19 -24
- package/dist/src/Evolu/Owner.d.ts +6 -1
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +5 -0
- package/dist/src/Evolu/Protocol.d.ts +134 -64
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +227 -77
- package/dist/src/Evolu/Relay.d.ts +3 -1
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +39 -3
- package/dist/src/Evolu/Schema.d.ts +24 -40
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +13 -72
- package/dist/src/Evolu/Storage.d.ts +1 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +10 -0
- package/package.json +3 -3
- package/src/Assert.ts +6 -3
- package/src/Crypto.ts +13 -0
- package/src/Evolu/Config.ts +19 -5
- package/src/Evolu/Db.ts +92 -76
- package/src/Evolu/Evolu.ts +38 -35
- package/src/Evolu/Owner.ts +7 -1
- package/src/Evolu/Protocol.ts +276 -110
- package/src/Evolu/Relay.ts +45 -4
- package/src/Evolu/Schema.ts +102 -130
- package/src/Evolu/Storage.ts +12 -0
package/src/Evolu/Db.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
SqliteValue,
|
|
35
35
|
} from "../Sqlite.js";
|
|
36
36
|
import { TimeDep } from "../Time.js";
|
|
37
|
-
import { Id, Mnemonic, object, SimpleName, String } from "../Type.js";
|
|
37
|
+
import { array, Id, Mnemonic, object, SimpleName, String } from "../Type.js";
|
|
38
38
|
import {
|
|
39
39
|
createInitializedWorker,
|
|
40
40
|
Worker,
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
Base64Url256,
|
|
55
55
|
BinaryId,
|
|
56
56
|
binaryIdToId,
|
|
57
|
+
BinaryOwnerId,
|
|
57
58
|
CrdtMessage,
|
|
58
59
|
createProtocolMessageForSync,
|
|
59
60
|
createProtocolMessageFromCrdtMessages,
|
|
@@ -96,19 +97,21 @@ import {
|
|
|
96
97
|
timestampToTimestampString,
|
|
97
98
|
} from "./Timestamp.js";
|
|
98
99
|
|
|
99
|
-
export
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface DbTable {
|
|
105
|
-
readonly name: Base64Url256;
|
|
106
|
-
readonly columns: ReadonlyArray<Base64Url256>;
|
|
107
|
-
}
|
|
100
|
+
export const DbTable = object({
|
|
101
|
+
name: Base64Url256,
|
|
102
|
+
columns: array(Base64Url256),
|
|
103
|
+
});
|
|
104
|
+
export type DbTable = typeof DbTable.Type;
|
|
108
105
|
|
|
109
106
|
export const DbIndex = object({ name: String, sql: String });
|
|
110
107
|
export type DbIndex = typeof DbIndex.Type;
|
|
111
108
|
|
|
109
|
+
export const DbSchema = object({
|
|
110
|
+
tables: array(DbTable),
|
|
111
|
+
indexes: array(DbIndex),
|
|
112
|
+
});
|
|
113
|
+
export type DbSchema = typeof DbSchema.Type;
|
|
114
|
+
|
|
112
115
|
export type DbWorker = Worker<DbWorkerInput, DbWorkerOutput>;
|
|
113
116
|
|
|
114
117
|
export type CreateDbWorker = (name: SimpleName) => DbWorker;
|
|
@@ -241,6 +244,7 @@ export const createDbWorkerForPlatform = (
|
|
|
241
244
|
|
|
242
245
|
const sqliteResult = await createSqlite(platformDeps)(
|
|
243
246
|
initMessage.config.name,
|
|
247
|
+
{ memory: initMessage.config.inMemory ?? false },
|
|
244
248
|
);
|
|
245
249
|
|
|
246
250
|
if (!sqliteResult.ok) {
|
|
@@ -496,7 +500,6 @@ export const createDbWorkerForPlatform = (
|
|
|
496
500
|
onCompleteId: message.onCompleteId,
|
|
497
501
|
reload: message.reload,
|
|
498
502
|
});
|
|
499
|
-
deps.sqlite[Symbol.dispose]();
|
|
500
503
|
|
|
501
504
|
break;
|
|
502
505
|
}
|
|
@@ -621,34 +624,32 @@ const indexesAreEqual = (self: DbIndex, that: DbIndex): boolean =>
|
|
|
621
624
|
|
|
622
625
|
export interface DbSnapshot {
|
|
623
626
|
readonly schema: DbSchema;
|
|
624
|
-
readonly
|
|
625
|
-
rows: ReadonlyArray<SqliteRow>;
|
|
627
|
+
readonly tables: Array<{
|
|
626
628
|
name: string;
|
|
629
|
+
rows: ReadonlyArray<SqliteRow>;
|
|
627
630
|
}>;
|
|
628
631
|
}
|
|
629
632
|
|
|
630
|
-
// TODO: Move
|
|
631
|
-
export const getDbSnapshot = (
|
|
632
|
-
deps: SqliteDep,
|
|
633
|
-
): Result<DbSnapshot, SqliteError> => {
|
|
633
|
+
// TODO: Move to test helpers.
|
|
634
|
+
export const getDbSnapshot = (deps: SqliteDep): DbSnapshot => {
|
|
634
635
|
const schema = getDbSchema(deps)({ allIndexes: true });
|
|
635
|
-
|
|
636
|
+
assert(schema.ok, "bug");
|
|
636
637
|
|
|
637
|
-
const
|
|
638
|
+
const tables = [];
|
|
638
639
|
|
|
639
640
|
for (const table of schema.value.tables) {
|
|
640
641
|
const result = deps.sqlite.exec(sql`
|
|
641
642
|
select * from ${sql.identifier(table.name)};
|
|
642
643
|
`);
|
|
643
|
-
|
|
644
|
+
assert(result.ok, "bug");
|
|
644
645
|
|
|
645
|
-
|
|
646
|
-
rows: result.value.rows,
|
|
646
|
+
tables.push({
|
|
647
647
|
name: table.name,
|
|
648
|
+
rows: result.value.rows,
|
|
648
649
|
});
|
|
649
650
|
}
|
|
650
651
|
|
|
651
|
-
return
|
|
652
|
+
return { schema: schema.value, tables };
|
|
652
653
|
};
|
|
653
654
|
|
|
654
655
|
const ensureDbSchema =
|
|
@@ -787,41 +788,39 @@ const initializeDb =
|
|
|
787
788
|
`,
|
|
788
789
|
|
|
789
790
|
/**
|
|
790
|
-
* The History table stores all values per timestamp, table,
|
|
791
|
-
* column
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
* and DbChange to leverage a covering index. Hence, every value change
|
|
796
|
-
* has its timestamp, table, row, and column. In the future, we will
|
|
797
|
-
* rethink that and store history more efficiently.
|
|
798
|
-
*
|
|
799
|
-
* There is no need to use `OwnerId` on the client, because timestamps
|
|
800
|
-
* (which include a `NodeId`) are globally unique. The relay needs
|
|
801
|
-
* `OwnerId` because it hosts multiple apps/owners, but client storage
|
|
802
|
-
* represents one DB, and even when it hosts multiple owners, their
|
|
803
|
-
* timestamps remain unique.
|
|
791
|
+
* The History table stores all values per ownerId, timestamp, table, id,
|
|
792
|
+
* and column for conflict-free merging using last-write-win CRDT.
|
|
793
|
+
* Denormalizes Timestamp and DbChange for covering index performance.
|
|
794
|
+
* Time travel is available when last-write-win isn't desired. Future
|
|
795
|
+
* optimization will store history more efficiently.
|
|
804
796
|
*/
|
|
805
797
|
sql`
|
|
806
798
|
create table evolu_history (
|
|
807
|
-
"
|
|
799
|
+
"ownerId" blob not null,
|
|
808
800
|
"table" text not null,
|
|
809
|
-
"
|
|
801
|
+
"id" blob not null,
|
|
810
802
|
"column" text not null,
|
|
803
|
+
"timestamp" blob not null,
|
|
811
804
|
"value" any
|
|
812
805
|
)
|
|
813
806
|
strict;
|
|
814
807
|
`,
|
|
815
808
|
|
|
809
|
+
// Index for reading database changes by owner and timestamp.
|
|
810
|
+
// Timestamp always corresponds to a DbChange.
|
|
816
811
|
sql`
|
|
817
|
-
create index
|
|
812
|
+
create index evolu_history_ownerId_timestamp on evolu_history (
|
|
813
|
+
"ownerId",
|
|
814
|
+
"timestamp"
|
|
815
|
+
);
|
|
818
816
|
`,
|
|
819
817
|
|
|
820
818
|
sql`
|
|
821
|
-
create unique index
|
|
822
|
-
"
|
|
823
|
-
"column",
|
|
819
|
+
create unique index evolu_history_ownerId_table_id_column_timestampDesc on evolu_history (
|
|
820
|
+
"ownerId",
|
|
824
821
|
"table",
|
|
822
|
+
"id",
|
|
823
|
+
"column",
|
|
825
824
|
"timestamp" desc
|
|
826
825
|
);
|
|
827
826
|
`,
|
|
@@ -847,7 +846,14 @@ const initializeDb =
|
|
|
847
846
|
|
|
848
847
|
const result = deps.sqlite.exec(sql`
|
|
849
848
|
insert into evolu_owner
|
|
850
|
-
(
|
|
849
|
+
(
|
|
850
|
+
"mnemonic",
|
|
851
|
+
"id",
|
|
852
|
+
"encryptionKey",
|
|
853
|
+
"createdAt",
|
|
854
|
+
"writeKey",
|
|
855
|
+
"timestamp"
|
|
856
|
+
)
|
|
851
857
|
values
|
|
852
858
|
(
|
|
853
859
|
${ownerRow.mnemonic},
|
|
@@ -910,16 +916,18 @@ const applyMessages =
|
|
|
910
916
|
(
|
|
911
917
|
messages: ReadonlyArray<CrdtMessage>,
|
|
912
918
|
lastTimestamp: Timestamp,
|
|
913
|
-
options: { isMigrationFromVersion0?: boolean } = {},
|
|
914
919
|
): Result<void, SqliteError> => {
|
|
920
|
+
const ownerId = ownerIdToBinaryOwnerId(deps.ownerRowRef.get().id);
|
|
921
|
+
|
|
915
922
|
for (const message of messages) {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (!apply1.ok) return apply1;
|
|
919
|
-
}
|
|
923
|
+
const result1 = applyMessageToAppTable(deps)(ownerId, message);
|
|
924
|
+
if (!result1.ok) return result1;
|
|
920
925
|
|
|
921
|
-
const
|
|
922
|
-
|
|
926
|
+
const result2 = applyMessageToTimestampAndHistoryTables(deps)(
|
|
927
|
+
ownerId,
|
|
928
|
+
message,
|
|
929
|
+
);
|
|
930
|
+
if (!result2.ok) return result2;
|
|
923
931
|
}
|
|
924
932
|
|
|
925
933
|
const timestamp = timestampToTimestampString(lastTimestamp);
|
|
@@ -933,8 +941,8 @@ const applyMessages =
|
|
|
933
941
|
};
|
|
934
942
|
|
|
935
943
|
const applyMessageToAppTable =
|
|
936
|
-
(deps: SqliteDep) =>
|
|
937
|
-
(message: CrdtMessage): Result<void, SqliteError> => {
|
|
944
|
+
(deps: SqliteDep & OwnerRowRefDep) =>
|
|
945
|
+
(ownerId: BinaryOwnerId, message: CrdtMessage): Result<void, SqliteError> => {
|
|
938
946
|
const date = new Date(message.timestamp.millis).toISOString();
|
|
939
947
|
const timestamp = timestampToBinaryTimestamp(message.timestamp);
|
|
940
948
|
|
|
@@ -942,28 +950,29 @@ const applyMessageToAppTable =
|
|
|
942
950
|
const result = deps.sqlite.exec(sql.prepared`
|
|
943
951
|
with
|
|
944
952
|
lastTimestamp as (
|
|
945
|
-
select timestamp
|
|
953
|
+
select "timestamp"
|
|
946
954
|
from evolu_history
|
|
947
955
|
where
|
|
948
|
-
"
|
|
949
|
-
and "column" = ${column}
|
|
956
|
+
"ownerId" = ${ownerId}
|
|
950
957
|
and "table" = ${message.change.table}
|
|
951
|
-
|
|
958
|
+
and "id" = ${message.change.id}
|
|
959
|
+
and "column" = ${column}
|
|
960
|
+
order by "timestamp" desc
|
|
952
961
|
limit 1
|
|
953
962
|
)
|
|
954
963
|
insert into ${sql.identifier(message.change.table)}
|
|
955
964
|
("id", ${sql.identifier(column)}, createdAt, updatedAt)
|
|
956
965
|
select ${message.change.id}, ${value}, ${date}, ${date}
|
|
957
966
|
where
|
|
958
|
-
(select timestamp from lastTimestamp) is null
|
|
959
|
-
or (select timestamp from lastTimestamp) < ${timestamp}
|
|
967
|
+
(select "timestamp" from lastTimestamp) is null
|
|
968
|
+
or (select "timestamp" from lastTimestamp) < ${timestamp}
|
|
960
969
|
on conflict ("id") do update
|
|
961
970
|
set
|
|
962
971
|
${sql.identifier(column)} = ${value},
|
|
963
972
|
updatedAt = ${date}
|
|
964
973
|
where
|
|
965
|
-
(select timestamp from lastTimestamp) is null
|
|
966
|
-
or (select timestamp from lastTimestamp) < ${timestamp};
|
|
974
|
+
(select "timestamp" from lastTimestamp) is null
|
|
975
|
+
or (select "timestamp" from lastTimestamp) < ${timestamp};
|
|
967
976
|
`);
|
|
968
977
|
|
|
969
978
|
if (!result.ok) return result;
|
|
@@ -973,10 +982,9 @@ const applyMessageToAppTable =
|
|
|
973
982
|
};
|
|
974
983
|
|
|
975
984
|
export const applyMessageToTimestampAndHistoryTables =
|
|
976
|
-
(deps: SqliteDep &
|
|
977
|
-
(message: CrdtMessage): Result<void, SqliteError> => {
|
|
985
|
+
(deps: SqliteDep & ClientStorageDep) =>
|
|
986
|
+
(ownerId: BinaryOwnerId, message: CrdtMessage): Result<void, SqliteError> => {
|
|
978
987
|
const timestamp = timestampToBinaryTimestamp(message.timestamp);
|
|
979
|
-
const ownerId = ownerIdToBinaryOwnerId(deps.ownerRowRef.get().id);
|
|
980
988
|
const id = idToBinaryId(message.change.id);
|
|
981
989
|
|
|
982
990
|
const result = deps.storage.insertTimestamp(ownerId, timestamp);
|
|
@@ -985,9 +993,16 @@ export const applyMessageToTimestampAndHistoryTables =
|
|
|
985
993
|
for (const [column, value] of Object.entries(message.change.values)) {
|
|
986
994
|
const result = deps.sqlite.exec(sql.prepared`
|
|
987
995
|
insert into evolu_history
|
|
988
|
-
("
|
|
996
|
+
("ownerId", "table", "id", "column", "value", "timestamp")
|
|
989
997
|
values
|
|
990
|
-
(
|
|
998
|
+
(
|
|
999
|
+
${ownerId},
|
|
1000
|
+
${message.change.table},
|
|
1001
|
+
${id},
|
|
1002
|
+
${column},
|
|
1003
|
+
${value},
|
|
1004
|
+
${timestamp}
|
|
1005
|
+
)
|
|
991
1006
|
on conflict do nothing;
|
|
992
1007
|
`);
|
|
993
1008
|
if (!result.ok) return result;
|
|
@@ -1063,11 +1078,11 @@ export const maybeMigrateToVersion0 =
|
|
|
1063
1078
|
const messagesRows = deps.sqlite.exec<{
|
|
1064
1079
|
timestamp: TimestampString;
|
|
1065
1080
|
table: Base64Url256;
|
|
1066
|
-
|
|
1081
|
+
id: Id;
|
|
1067
1082
|
column: Base64Url256;
|
|
1068
1083
|
value: SqliteValue;
|
|
1069
1084
|
}>(sql`
|
|
1070
|
-
select "timestamp", "table", "
|
|
1085
|
+
select "timestamp", "table", "id", "column", "value" from evolu_message;
|
|
1071
1086
|
`);
|
|
1072
1087
|
|
|
1073
1088
|
if (!messagesRows.ok) return messagesRows;
|
|
@@ -1083,7 +1098,7 @@ export const maybeMigrateToVersion0 =
|
|
|
1083
1098
|
const messages = messagesRows.value.rows.map((message) => ({
|
|
1084
1099
|
timestamp: timestampStringToTimestamp(message.timestamp),
|
|
1085
1100
|
change: {
|
|
1086
|
-
id: message.
|
|
1101
|
+
id: message.id,
|
|
1087
1102
|
table: message.table,
|
|
1088
1103
|
values: { [message.column]: message.value },
|
|
1089
1104
|
},
|
|
@@ -1176,6 +1191,7 @@ const createClientStorage =
|
|
|
1176
1191
|
...sqliteStorageBase.value,
|
|
1177
1192
|
|
|
1178
1193
|
validateWriteKey: constFalse,
|
|
1194
|
+
setWriteKey: constFalse,
|
|
1179
1195
|
|
|
1180
1196
|
writeMessages: (_ownerId, messages) => {
|
|
1181
1197
|
// TODO: Get owner by _ownerId when we support more.
|
|
@@ -1231,16 +1247,16 @@ const createClientStorage =
|
|
|
1231
1247
|
return true;
|
|
1232
1248
|
},
|
|
1233
1249
|
|
|
1234
|
-
readDbChange: (
|
|
1250
|
+
readDbChange: (ownerId, timestamp) => {
|
|
1235
1251
|
const result = deps.sqlite.exec<{
|
|
1236
1252
|
table: Base64Url256;
|
|
1237
|
-
|
|
1253
|
+
id: BinaryId;
|
|
1238
1254
|
column: Base64Url256;
|
|
1239
1255
|
value: SqliteValue;
|
|
1240
1256
|
}>(sql`
|
|
1241
|
-
select "table", "
|
|
1257
|
+
select "table", "id", "column", "value"
|
|
1242
1258
|
from evolu_history
|
|
1243
|
-
where "timestamp" = ${timestamp};
|
|
1259
|
+
where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
|
|
1244
1260
|
`);
|
|
1245
1261
|
if (!result.ok) {
|
|
1246
1262
|
deps.postMessage({ type: "onError", error: result.error });
|
|
@@ -1250,18 +1266,18 @@ const createClientStorage =
|
|
|
1250
1266
|
const { rows } = result.value;
|
|
1251
1267
|
assert(rows.length > 0, "Rows must not be empty");
|
|
1252
1268
|
|
|
1253
|
-
const { table,
|
|
1269
|
+
const { table, id } = rows[0];
|
|
1254
1270
|
const values: Record<string, SqliteValue> = {};
|
|
1255
1271
|
|
|
1256
1272
|
for (const r of rows) {
|
|
1257
1273
|
assert(r.table === table, "All rows must have the same table");
|
|
1258
|
-
assert(eqArrayNumber(r.
|
|
1274
|
+
assert(eqArrayNumber(r.id, id), "All rows must have the same Id");
|
|
1259
1275
|
values[r.column] = r.value;
|
|
1260
1276
|
}
|
|
1261
1277
|
|
|
1262
1278
|
const change: DbChange = {
|
|
1263
1279
|
table: rows[0].table,
|
|
1264
|
-
id: binaryIdToId(rows[0].
|
|
1280
|
+
id: binaryIdToId(rows[0].id),
|
|
1265
1281
|
values,
|
|
1266
1282
|
};
|
|
1267
1283
|
|
package/src/Evolu/Evolu.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isNonEmptyArray, isNonEmptyReadonlyArray } from "../Array.js";
|
|
2
|
-
import { assertNonEmptyArray } from "../Assert.js";
|
|
2
|
+
import { assert, assertNonEmptyArray } from "../Assert.js";
|
|
3
3
|
import { createCallbacks } from "../Callbacks.js";
|
|
4
4
|
import { ConsoleDep } from "../Console.js";
|
|
5
5
|
import { SymmetricCryptoDecryptError } from "../Crypto.js";
|
|
@@ -44,9 +44,9 @@ import {
|
|
|
44
44
|
SubscribedQueries,
|
|
45
45
|
} from "./Query.js";
|
|
46
46
|
import {
|
|
47
|
-
assertValidEvoluSchema,
|
|
48
47
|
CreateQuery,
|
|
49
48
|
EvoluSchema,
|
|
49
|
+
evoluSchemaToDbSchema,
|
|
50
50
|
insertable,
|
|
51
51
|
Mutation,
|
|
52
52
|
MutationKind,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
MutationOptions,
|
|
55
55
|
updateable,
|
|
56
56
|
upsertable,
|
|
57
|
-
|
|
57
|
+
ValidateSchema,
|
|
58
58
|
ValidMutationSize,
|
|
59
59
|
ValidMutationSizeError,
|
|
60
60
|
} from "./Schema.js";
|
|
@@ -88,7 +88,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
88
88
|
* All this function does is compile the Kysely query and serialize it into a
|
|
89
89
|
* unique string. Both operations are fast and cheap.
|
|
90
90
|
*
|
|
91
|
-
* For mutations, use {@link Evolu
|
|
91
|
+
* For mutations, use {@link Evolu#insert} and {@link Evolu#update}.
|
|
92
92
|
*
|
|
93
93
|
* ### Example
|
|
94
94
|
*
|
|
@@ -111,7 +111,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
111
111
|
* A returned promise always resolves successfully because there is no reason
|
|
112
112
|
* why loading should fail. All data are local, and the query is typed. A
|
|
113
113
|
* serious unexpected Evolu error shall be handled with
|
|
114
|
-
* {@link Evolu
|
|
114
|
+
* {@link Evolu#subscribeError}.
|
|
115
115
|
*
|
|
116
116
|
* Loading is batched, and returned promises are cached, so there is no need
|
|
117
117
|
* for an additional cache. Evolu's internal cache is invalidated on
|
|
@@ -144,7 +144,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
144
144
|
* If you are curious why Evolu does not do that for all queries by default,
|
|
145
145
|
* the answer is simple: performance. Tracking changes is costly and
|
|
146
146
|
* meaningful only for visible (hence subscribed) queries anyway. To subscribe
|
|
147
|
-
* to a query, use {@link Evolu
|
|
147
|
+
* to a query, use {@link Evolu#subscribeQuery}.
|
|
148
148
|
*
|
|
149
149
|
* ### Example
|
|
150
150
|
*
|
|
@@ -300,7 +300,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> {
|
|
|
300
300
|
|
|
301
301
|
/**
|
|
302
302
|
* Restore {@link AppOwner} with all their synced data. It uses
|
|
303
|
-
* {@link Evolu
|
|
303
|
+
* {@link Evolu#resetAppOwner}, so be careful.
|
|
304
304
|
*/
|
|
305
305
|
readonly restoreAppOwner: (
|
|
306
306
|
mnemonic: Mnemonic,
|
|
@@ -431,9 +431,7 @@ let tabId: Id | null = null;
|
|
|
431
431
|
export const createEvolu =
|
|
432
432
|
(deps: EvoluDeps) =>
|
|
433
433
|
<S extends EvoluSchema>(
|
|
434
|
-
|
|
435
|
-
// with type errors messages as we had it in the old Evolu.
|
|
436
|
-
schema: S,
|
|
434
|
+
schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>,
|
|
437
435
|
partialConfig: Partial<EvoluConfigWithInitialData<S>> = {},
|
|
438
436
|
): Evolu<S> => {
|
|
439
437
|
const config = { ...defaultConfig, ...partialConfig };
|
|
@@ -441,11 +439,14 @@ export const createEvolu =
|
|
|
441
439
|
let evolu = evoluInstances.get(config.name);
|
|
442
440
|
|
|
443
441
|
if (evolu == null) {
|
|
444
|
-
evolu = createEvoluInstance(deps)(
|
|
442
|
+
evolu = createEvoluInstance(deps)(
|
|
443
|
+
schema as EvoluSchema,
|
|
444
|
+
config as IntentionalNever,
|
|
445
|
+
);
|
|
445
446
|
evoluInstances.set(config.name, evolu);
|
|
446
447
|
} else {
|
|
447
448
|
// Hot reloading. Note that indexes are intentionally omitted.
|
|
448
|
-
evolu.ensureSchema(schema);
|
|
449
|
+
evolu.ensureSchema(schema as EvoluSchema);
|
|
449
450
|
}
|
|
450
451
|
|
|
451
452
|
return evolu as IntentionalNever;
|
|
@@ -554,10 +555,7 @@ const createEvoluInstance =
|
|
|
554
555
|
}
|
|
555
556
|
});
|
|
556
557
|
|
|
557
|
-
const dbSchema =
|
|
558
|
-
assertValidEvoluSchema(schema),
|
|
559
|
-
indexes,
|
|
560
|
-
);
|
|
558
|
+
const dbSchema = evoluSchemaToDbSchema(schema, indexes);
|
|
561
559
|
|
|
562
560
|
const mutationTypesCache = new Map<
|
|
563
561
|
MutationKind,
|
|
@@ -592,21 +590,17 @@ const createEvoluInstance =
|
|
|
592
590
|
if (initialData)
|
|
593
591
|
initialData({
|
|
594
592
|
insert: (table, props) => {
|
|
595
|
-
const Type = getMutationType(table, "insert");
|
|
596
593
|
const id = createId(deps);
|
|
594
|
+
const values = getMutationType(table, "insert").fromUnknown(props);
|
|
597
595
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
initialDataDbChanges.push(
|
|
602
|
-
id,
|
|
603
|
-
table,
|
|
604
|
-
values: result.value,
|
|
605
|
-
} as unknown as DbChange);
|
|
596
|
+
if (values.ok) {
|
|
597
|
+
const dbChange = { table, id, values: values.value };
|
|
598
|
+
assertValidDbChange(dbChange);
|
|
599
|
+
initialDataDbChanges.push(dbChange);
|
|
606
600
|
return ok({ id });
|
|
607
601
|
}
|
|
608
602
|
|
|
609
|
-
return
|
|
603
|
+
return values;
|
|
610
604
|
},
|
|
611
605
|
});
|
|
612
606
|
|
|
@@ -654,14 +648,14 @@ const createEvoluInstance =
|
|
|
654
648
|
} else {
|
|
655
649
|
// Remove `id` from values.
|
|
656
650
|
const { id: _id, ...values } = result.value;
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
mutateMicrotaskQueue.push([
|
|
651
|
+
const dbChange = { table, id, values };
|
|
652
|
+
assertValidDbChange(dbChange);
|
|
653
|
+
mutateMicrotaskQueue.push([dbChange, options?.onComplete]);
|
|
660
654
|
}
|
|
661
655
|
|
|
662
656
|
if (mutateMicrotaskQueue.length === 1)
|
|
663
657
|
queueMicrotask(() => {
|
|
664
|
-
const changes = [];
|
|
658
|
+
const changes: Array<DbChange> = [];
|
|
665
659
|
const onCompletes = [];
|
|
666
660
|
|
|
667
661
|
for (const [change, onComplete] of mutateMicrotaskQueue) {
|
|
@@ -807,6 +801,7 @@ const createEvoluInstance =
|
|
|
807
801
|
const onCompleteId = callbacks.register(() => {
|
|
808
802
|
resolve();
|
|
809
803
|
});
|
|
804
|
+
|
|
810
805
|
dbWorker.postMessage({
|
|
811
806
|
type: "reset",
|
|
812
807
|
onCompleteId,
|
|
@@ -822,11 +817,8 @@ const createEvoluInstance =
|
|
|
822
817
|
|
|
823
818
|
ensureSchema: (schema) => {
|
|
824
819
|
mutationTypesCache.clear();
|
|
825
|
-
const
|
|
826
|
-
dbWorker.postMessage({
|
|
827
|
-
type: "ensureDbSchema",
|
|
828
|
-
dbSchema: validEvoluSchemaToDbSchema(validSchema),
|
|
829
|
-
});
|
|
820
|
+
const dbSchema = evoluSchemaToDbSchema(schema);
|
|
821
|
+
dbWorker.postMessage({ type: "ensureDbSchema", dbSchema });
|
|
830
822
|
},
|
|
831
823
|
|
|
832
824
|
exportDatabase: () => {
|
|
@@ -945,3 +937,14 @@ const createLoadingPromises = (
|
|
|
945
937
|
|
|
946
938
|
return loadingPromises;
|
|
947
939
|
};
|
|
940
|
+
|
|
941
|
+
const assertValidDbChange: (dbChange: {
|
|
942
|
+
table: string;
|
|
943
|
+
id: Id;
|
|
944
|
+
values: unknown;
|
|
945
|
+
}) => asserts dbChange is DbChange = (dbChange) => {
|
|
946
|
+
assert(
|
|
947
|
+
DbChange.is(dbChange),
|
|
948
|
+
`Failed to create DbChange for table "${dbChange.table}". If you see this message, you either disabled EvoluSchema validation or Evolu has a bug - please report it.`,
|
|
949
|
+
);
|
|
950
|
+
};
|
package/src/Evolu/Owner.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO:
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import { assert } from "../Assert.js";
|
|
2
8
|
import {
|
|
3
9
|
createEncryptionKey,
|
|
@@ -50,7 +56,7 @@ import {
|
|
|
50
56
|
* {@link SharedReadonlyOwner}, each with specific roles and properties detailed
|
|
51
57
|
* in their respective definitions.
|
|
52
58
|
*
|
|
53
|
-
* Public-key cryptography isn’t included here as it belongs to
|
|
59
|
+
* Public-key cryptography isn’t included here as it belongs to app and varies
|
|
54
60
|
* by use case. An Evolu app without collaboration doesn’t need it, while a
|
|
55
61
|
* Nostr-like app can leverage Nostr NIPs, or a super-safe app can use
|
|
56
62
|
* post-quantum cryptography.
|