@evolu/common 6.0.1-preview.2 → 6.0.1-preview.4

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.
@@ -130,7 +130,8 @@ import { eqArrayNumber } from "../Eq.js";
130
130
  import { computeBalancedBuckets } from "../Number.js";
131
131
  import { objectToEntries } from "../Object.js";
132
132
  import { err, ok } from "../Result.js";
133
- import { Base64Url, DateIsoString, idTypeValueLength, JsonValueFromString, maxLength, NonNegativeInt, Number, } from "../Type.js";
133
+ import { SqliteValue } from "../Sqlite.js";
134
+ import { Base64Url, DateIsoString, Id, idTypeValueLength, JsonValueFromString, maxLength, NonNegativeInt, Number, object, record, } from "../Type.js";
134
135
  import { writeKeyLength, } from "./Owner.js";
135
136
  import { binaryTimestampToTimestamp, Counter, Millis, timestampToBinaryTimestamp, } from "./Timestamp.js";
136
137
  /** Maximum size of the entire protocol message in bytes. */
@@ -148,6 +149,20 @@ export const ProtocolErrorCode = {
148
149
  /** A code for {@link ProtocolSyncError}. */
149
150
  SyncError: 3,
150
151
  };
152
+ /**
153
+ * Base64Url string with maximum length of 256 characters. Encoding strings as
154
+ * Base64UrlString saves up to 25% in size compared to regular strings.
155
+ */
156
+ export const Base64Url256 = maxLength(256)(Base64Url);
157
+ /**
158
+ * A DbChange is a change to a table row. Together with a unique
159
+ * {@link Timestamp}, it forms a {@link CrdtMessage}.
160
+ */
161
+ export const DbChange = object({
162
+ table: Base64Url256,
163
+ id: Id,
164
+ values: record(Base64Url256, SqliteValue),
165
+ });
151
166
  export const RangeType = {
152
167
  Fingerprint: 1,
153
168
  Skip: 0,
@@ -840,11 +855,6 @@ export const idToBinaryId = (id) => base64Url256ToBytes(id);
840
855
  export const binaryIdToId = (binaryId) => decodeId(createBuffer(binaryId));
841
856
  export const ownerIdToBinaryOwnerId = (ownerId) => base64Url256ToBytes(ownerId);
842
857
  export const binaryOwnerIdToOwnerId = (binaryOwnerId) => decodeOwnerId(createBuffer(binaryOwnerId));
843
- /**
844
- * Base64Url string with maximum length of 256 characters. Encoding strings as
845
- * Base64UrlString saves up to 25% in size compared to regular strings.
846
- */
847
- export const Base64Url256 = maxLength(256)(Base64Url);
848
858
  /**
849
859
  * Alphabet used for Base64Url encoding. This is copied from the `nanoid`
850
860
  * library to avoid dependency on a specific version of `nanoid`.
@@ -2,7 +2,7 @@ import { Kysely, SelectQueryBuilder } from "kysely";
2
2
  import { ReadonlyRecord } from "../Object.js";
3
3
  import { Result } from "../Result.js";
4
4
  import { SqliteBoolean, SqliteQueryOptions, SqliteValue } from "../Sqlite.js";
5
- import { AnyType, BrandType, InferErrors, InferInput, InferType, MergeObjectTypeErrors, NullableToOptionalProps, ObjectType, OptionalType, Type, TypeError } from "../Type.js";
5
+ import { AnyType, BrandType, IdType, InferErrors, InferInput, InferType, MergeObjectTypeErrors, NullableToOptionalProps, ObjectType, OptionalType, Type, TypeError } from "../Type.js";
6
6
  import { Simplify } from "../Types.js";
7
7
  import { DbSchema } from "./Db.js";
8
8
  import { DbIndexesBuilder } from "./Kysely.js";
@@ -13,12 +13,6 @@ import { BinaryTimestamp } from "./Timestamp.js";
13
13
  /**
14
14
  * Defines the schema of an Evolu database.
15
15
  *
16
- * - Each top-level key represents a table name.
17
- * - The value for each table name is a record of column names mapped to their
18
- * respective data types, defined by {@link Type}.
19
- * - Each table must include a mandatory `id` column of type {@link Id}.
20
- * - No table may contain {@link DefaultColumns}.
21
- *
22
16
  * Table schema defines columns that are required for table rows. For not
23
17
  * required columns, use {@link nullOr}.
24
18
  *
@@ -50,9 +44,28 @@ import { BinaryTimestamp } from "./Timestamp.js";
50
44
  * };
51
45
  * ```
52
46
  */
53
- export type EvoluSchema = ReadonlyRecord<string, ReadonlyRecord<string, Type<any, any, any, any, any>> & {
54
- readonly id: Type<any, any, any, any, any>;
55
- }>;
47
+ export type EvoluSchema = ReadonlyRecord<string, ReadonlyRecord<string, Type<any, any, any, any, any>>>;
48
+ /**
49
+ * Validates an {@link EvoluSchema} at compile time, returning the first error
50
+ * found as a readable string literal type. This approach provides much clearer
51
+ * and more actionable TypeScript errors than the default, which are often hard
52
+ * to read.
53
+ *
54
+ * Validates the following schema requirements:
55
+ *
56
+ * 1. All tables must have an 'id' column
57
+ * 2. The 'id' column must be a branded ID type (created with id() function)
58
+ * 3. Tables cannot use default column names (createdAt, updatedAt, isDeleted)
59
+ * 4. All column types must be compatible with SQLite (extend SqliteValue)
60
+ */
61
+ export type ValidateSchema<S extends EvoluSchema> = ValidateSchemaHasId<S> extends never ? ValidateIdColumnType<S> extends never ? ValidateNoDefaultColumns<S> extends never ? ValidateColumnTypes<S> extends never ? S : ValidateColumnTypes<S> : ValidateNoDefaultColumns<S> : ValidateIdColumnType<S> : ValidateSchemaHasId<S>;
62
+ export type ValidateSchemaHasId<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? "id" extends keyof S[TableName] ? never : SchemaValidationError<`Table "${TableName & string}" is missing required id column.`> : never : never;
63
+ export type ValidateIdColumnType<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? "id" extends keyof S[TableName] ? S[TableName]["id"] extends IdType<any> ? never : SchemaValidationError<`Table "${TableName & string}" id column must be a branded ID type (created with id("${TableName & string}")).`> : never : never : never;
64
+ export type ValidateNoDefaultColumns<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? keyof S[TableName] extends infer ColumnName ? ColumnName extends keyof S[TableName] ? ColumnName extends "createdAt" | "updatedAt" | "isDeleted" ? SchemaValidationError<`Table "${TableName & string}" uses default column name "${ColumnName & string}". Default columns (createdAt, updatedAt, isDeleted) are added automatically.`> : never : never : never : never : never;
65
+ export type ValidateColumnTypes<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? keyof S[TableName] extends infer ColumnName ? ColumnName extends keyof S[TableName] ? InferType<S[TableName][ColumnName]> extends SqliteValue ? never : SchemaValidationError<`Table "${TableName & string}" column "${ColumnName & string}" type is not compatible with SQLite. Column types must extend SqliteValue (string, number, Uint8Array, or null).`> : never : never : never : never;
66
+ /** Schema validation error that shows clear, readable messages */
67
+ export type SchemaValidationError<Message extends string> = `❌ Schema Error: ${Message}`;
68
+ export declare const evoluSchemaToDbSchema: (schema: EvoluSchema, indexes?: DbIndexesBuilder) => DbSchema;
56
69
  export type CreateQuery<S extends EvoluSchema> = <R extends Row>(queryCallback: (db: Pick<Kysely<{
57
70
  [Table in keyof S]: {
58
71
  readonly [Column in keyof S[Table]]: Column extends "id" | "createdAt" | "updatedAt" ? InferType<S[Table][Column]> : InferType<S[Table][Column]> | null;
@@ -61,7 +74,7 @@ export type CreateQuery<S extends EvoluSchema> = <R extends Row>(queryCallback:
61
74
  readonly evolu_history: {
62
75
  readonly timestamp: BinaryTimestamp;
63
76
  readonly table: keyof S;
64
- readonly row: BinaryId;
77
+ readonly id: BinaryId;
65
78
  readonly column: string;
66
79
  readonly value: SqliteValue;
67
80
  };
@@ -72,35 +85,6 @@ export declare const DefaultColumns: ObjectType<{
72
85
  isDeleted: import("../Type.js").UnionType<[Type<"Null", null, null, import("../Type.js").NullError, null, import("../Type.js").NullError>, import("../Type.js").TransformType<Type<"Boolean", boolean, boolean, import("../Type.js").BooleanError, boolean, import("../Type.js").BooleanError>, import("../Type.js").UnionType<[import("../Type.js").LiteralType<0>, import("../Type.js").LiteralType<1>]>, never>]>;
73
86
  }>;
74
87
  export type DefaultColumns = typeof DefaultColumns.Type;
75
- /**
76
- * Valid {@link EvoluSchema}.
77
- *
78
- * - Table and column names must be Base64Url strings.
79
- * - Each table must include an `id` column of type {@link Id}.
80
- * - Default column names (`createdAt`, `updatedAt`, `isDeleted`) are not allowed.
81
- */
82
- export declare const ValidEvoluSchema: BrandType<import("../Type.js").RecordType<"Brand", string & import("../Types.js").Brand<"Base64Url"> & import("../Types.js").Brand<"MaxLength256">, string, import("../Type.js").MaxLengthError<256>, string & import("../Types.js").Brand<"Base64Url">, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, import("../Type.js").ObjectWithRecordType<{
83
- id: Type<"EvoluType", AnyType, AnyType, import("../Type.js").EvoluTypeError, AnyType, import("../Type.js").EvoluTypeError>;
84
- }, "Brand", string & import("../Types.js").Brand<"Base64Url"> & import("../Types.js").Brand<"MaxLength256">, string, import("../Type.js").MaxLengthError<256>, string & import("../Types.js").Brand<"Base64Url">, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, Type<"Unknown", unknown, unknown, never, unknown, never>>>, "ValidEvoluSchema", ValidEvoluSchemaError, import("../Type.js").RecordError<import("../Type.js").MaxLengthError<256>, import("../Type.js").ObjectWithRecordError<{
85
- id: import("../Type.js").EvoluTypeError;
86
- }, import("../Type.js").MaxLengthError<256>, never>> | import("../Type.js").RecordError<import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, import("../Type.js").ObjectWithRecordError<{
87
- id: import("../Type.js").EvoluTypeError;
88
- }, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, never>>>;
89
- export type ValidEvoluSchema = typeof ValidEvoluSchema.Type;
90
- export interface ValidEvoluSchemaError extends TypeError<"ValidEvoluSchema"> {
91
- readonly reason: {
92
- kind: "DefaultColumnError";
93
- tableName: string;
94
- columnName: string;
95
- };
96
- }
97
- /**
98
- * Asserts that the given value is {@link ValidEvoluSchema}.
99
- *
100
- * Throws an error if the value is not a valid Evolu schema.
101
- */
102
- export declare const assertValidEvoluSchema: (value: unknown) => ValidEvoluSchema;
103
- export declare const validEvoluSchemaToDbSchema: (validEvoluSchema: ValidEvoluSchema, indexes?: DbIndexesBuilder) => DbSchema;
104
88
  export type MutationKind = "insert" | "update" | "upsert";
105
89
  export type Mutation<S extends EvoluSchema, Kind extends MutationKind> = <TableName extends keyof S>(table: TableName, props: InferInput<ObjectType<MutationMapping<S[TableName], Kind>>>, options?: MutationOptions) => Result<{
106
90
  readonly id: S[TableName]["id"]["Type"];
@@ -1 +1 @@
1
- {"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Schema.ts"],"names":[],"mappings":"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"}
@@ -1,86 +1,27 @@
1
1
  import { pack } from "msgpackr";
2
+ import { assert } from "../Assert.js";
2
3
  import { mapObject, objectToEntries } from "../Object.js";
3
4
  import { err, ok } from "../Result.js";
4
5
  import { SqliteBoolean } from "../Sqlite.js";
5
- import { brand, createTypeErrorFormatter, DateIsoString, EvoluType, nullableToOptional, nullOr, object, omit, optional, record, Unknown, } from "../Type.js";
6
+ import { brand, createTypeErrorFormatter, DateIsoString, nullableToOptional, nullOr, object, omit, optional, } from "../Type.js";
7
+ import { DbSchema } from "./Db.js";
6
8
  import { createIndexes } from "./Kysely.js";
7
- import { Base64Url256, } from "./Protocol.js";
8
- export const DefaultColumns = object({
9
- createdAt: DateIsoString,
10
- updatedAt: DateIsoString,
11
- isDeleted: nullOr(SqliteBoolean),
12
- });
13
- const isDefaultColumnName = (value) => value === "createdAt" || value === "updatedAt" || value === "isDeleted";
14
- /**
15
- * Valid {@link EvoluSchema}.
16
- *
17
- * - Table and column names must be Base64Url strings.
18
- * - Each table must include an `id` column of type {@link Id}.
19
- * - Default column names (`createdAt`, `updatedAt`, `isDeleted`) are not allowed.
20
- */
21
- export const ValidEvoluSchema = brand("ValidEvoluSchema", record(Base64Url256, object({ id: EvoluType }, record(Base64Url256, Unknown))), (value) => {
22
- for (const tableName in value) {
23
- for (const columnName in value[tableName]) {
24
- if (isDefaultColumnName(columnName)) {
25
- return err({
26
- type: "ValidEvoluSchema",
27
- value,
28
- reason: {
29
- kind: "DefaultColumnError",
30
- tableName,
31
- columnName,
32
- },
33
- });
34
- }
35
- }
36
- }
37
- return ok(value);
38
- });
39
- /**
40
- * Asserts that the given value is {@link ValidEvoluSchema}.
41
- *
42
- * Throws an error if the value is not a valid Evolu schema.
43
- */
44
- export const assertValidEvoluSchema = (value) => {
45
- const validEvoluSchema = ValidEvoluSchema.fromUnknown(value);
46
- if (!validEvoluSchema.ok) {
47
- const message = formatValidEvoluSchemaError(validEvoluSchema.error);
48
- throw new Error(`Invalid Evolu schema: ${message}`);
49
- }
50
- return validEvoluSchema.value;
51
- };
52
- const formatValidEvoluSchemaError = (error) => {
53
- if (error.type === "Record") {
54
- if (error.reason.kind === "Key") {
55
- return `The table "${error.reason.key}" has invalid name. A table name must be Base64Url256 string (A-Z, a-z, 0-9, -, _).`;
56
- }
57
- if (error.reason.kind === "Value" &&
58
- error.reason.error.reason.kind === "Props" &&
59
- error.reason.error.reason.errors.id?.type === "EvoluType") {
60
- return `The table "${error.reason.key}" has invalid ID column. Check examples.`;
61
- }
62
- if (error.reason.kind === "Value" &&
63
- error.reason.error.reason.kind === "IndexKey") {
64
- return `The table "${error.reason.key}" has invalid column name "${error.reason.error.reason.key}". A column name must be Base64Url256 string (A-Z, a-z, 0-9, -, _).`;
65
- }
66
- }
67
- if (error.type === "ValidEvoluSchema") {
68
- return `The table "${error.reason.tableName}" uses reserved column name "${error.reason.columnName}". Reserved column names are: createdAt, updatedAt, isDeleted.`;
69
- }
70
- return JSON.stringify(error, null, 2);
71
- };
72
- export const validEvoluSchemaToDbSchema = (validEvoluSchema, indexes) => {
73
- const tables = objectToEntries(validEvoluSchema).map(([tableName, table]) => ({
9
+ export const evoluSchemaToDbSchema = (schema, indexes) => {
10
+ const tables = objectToEntries(schema).map(([tableName, table]) => ({
74
11
  name: tableName,
75
12
  columns: objectToEntries(table)
76
13
  .filter(([k]) => k !== "id")
77
14
  .map(([k]) => k),
78
15
  }));
79
- return {
80
- tables,
81
- indexes: createIndexes(indexes),
82
- };
16
+ const dbSchema = { tables, indexes: createIndexes(indexes) };
17
+ assert(DbSchema.is(dbSchema), "Invalid EvoluSchema: Table and column names must use only characters A-Za-z0-9_- and be at most 256 characters long.");
18
+ return dbSchema;
83
19
  };
20
+ export const DefaultColumns = object({
21
+ createdAt: DateIsoString,
22
+ updatedAt: DateIsoString,
23
+ isDeleted: nullOr(SqliteBoolean),
24
+ });
84
25
  /**
85
26
  * Evolu has to limit the maximum mutation size. Otherwise, sync couldn't use
86
27
  * the {@link maxProtocolMessageRangesSize}. The max size is 640KB in bytes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "6.0.1-preview.2",
3
+ "version": "6.0.1-preview.4",
4
4
  "description": "TypeScript library and local-first framework",
5
5
  "keywords": [
6
6
  "evolu",
@@ -57,7 +57,7 @@
57
57
  "better-sqlite3": "^11.10.0",
58
58
  "shx": "^0.3.4",
59
59
  "typescript": "^5.8.3",
60
- "vitest": "^3.2.2",
60
+ "vitest": "^3.2.3",
61
61
  "ws": "^8.18.2",
62
62
  "@evolu/tsconfig": "0.0.2"
63
63
  },
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 interface DbSchema {
100
- readonly tables: ReadonlyArray<DbTable>;
101
- readonly indexes: ReadonlyArray<DbIndex>;
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;
@@ -496,7 +499,6 @@ export const createDbWorkerForPlatform = (
496
499
  onCompleteId: message.onCompleteId,
497
500
  reload: message.reload,
498
501
  });
499
- deps.sqlite[Symbol.dispose]();
500
502
 
501
503
  break;
502
504
  }
@@ -621,34 +623,32 @@ const indexesAreEqual = (self: DbIndex, that: DbIndex): boolean =>
621
623
 
622
624
  export interface DbSnapshot {
623
625
  readonly schema: DbSchema;
624
- readonly rows: Array<{
625
- rows: ReadonlyArray<SqliteRow>;
626
+ readonly tables: Array<{
626
627
  name: string;
628
+ rows: ReadonlyArray<SqliteRow>;
627
629
  }>;
628
630
  }
629
631
 
630
- // TODO: Move it to Sqlite.
631
- export const getDbSnapshot = (
632
- deps: SqliteDep,
633
- ): Result<DbSnapshot, SqliteError> => {
632
+ // TODO: Move to test helpers.
633
+ export const getDbSnapshot = (deps: SqliteDep): DbSnapshot => {
634
634
  const schema = getDbSchema(deps)({ allIndexes: true });
635
- if (!schema.ok) return schema;
635
+ assert(schema.ok, "bug");
636
636
 
637
- const rows = [];
637
+ const tables = [];
638
638
 
639
639
  for (const table of schema.value.tables) {
640
640
  const result = deps.sqlite.exec(sql`
641
641
  select * from ${sql.identifier(table.name)};
642
642
  `);
643
- if (!result.ok) return result;
643
+ assert(result.ok, "bug");
644
644
 
645
- rows.push({
646
- rows: result.value.rows,
645
+ tables.push({
647
646
  name: table.name,
647
+ rows: result.value.rows,
648
648
  });
649
649
  }
650
650
 
651
- return ok({ schema: schema.value, rows });
651
+ return { schema: schema.value, tables };
652
652
  };
653
653
 
654
654
  const ensureDbSchema =
@@ -787,41 +787,39 @@ const initializeDb =
787
787
  `,
788
788
 
789
789
  /**
790
- * The History table stores all values per timestamp, table, row, and
791
- * column. It's required for merging without conflicts. Evolu uses
792
- * last-write-win CRDT. In case of last-write-win value isn't what we
793
- * want, we can use time travel. The current implementation prefers
794
- * performance over storage size. The History table denormalizes Timestamp
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.
790
+ * The History table stores all values per ownerId, timestamp, table, id,
791
+ * and column for conflict-free merging using last-write-win CRDT.
792
+ * Denormalizes Timestamp and DbChange for covering index performance.
793
+ * Time travel is available when last-write-win isn't desired. Future
794
+ * optimization will store history more efficiently.
804
795
  */
805
796
  sql`
806
797
  create table evolu_history (
807
- "timestamp" blob not null,
798
+ "ownerId" blob not null,
808
799
  "table" text not null,
809
- "row" blob not null,
800
+ "id" blob not null,
810
801
  "column" text not null,
802
+ "timestamp" blob not null,
811
803
  "value" any
812
804
  )
813
805
  strict;
814
806
  `,
815
807
 
808
+ // Index for reading database changes by owner and timestamp.
809
+ // Timestamp always corresponds to a DbChange.
816
810
  sql`
817
- create index evolu_history_timestamp on evolu_history ("timestamp");
811
+ create index evolu_history_ownerId_timestamp on evolu_history (
812
+ "ownerId",
813
+ "timestamp"
814
+ );
818
815
  `,
819
816
 
820
817
  sql`
821
- create unique index evolu_history_row_column_table_timestampDesc on evolu_history (
822
- "row",
823
- "column",
818
+ create unique index evolu_history_ownerId_table_id_column_timestampDesc on evolu_history (
819
+ "ownerId",
824
820
  "table",
821
+ "id",
822
+ "column",
825
823
  "timestamp" desc
826
824
  );
827
825
  `,
@@ -847,7 +845,14 @@ const initializeDb =
847
845
 
848
846
  const result = deps.sqlite.exec(sql`
849
847
  insert into evolu_owner
850
- (mnemonic, id, encryptionKey, createdAt, writeKey, timestamp)
848
+ (
849
+ "mnemonic",
850
+ "id",
851
+ "encryptionKey",
852
+ "createdAt",
853
+ "writeKey",
854
+ "timestamp"
855
+ )
851
856
  values
852
857
  (
853
858
  ${ownerRow.mnemonic},
@@ -910,16 +915,18 @@ const applyMessages =
910
915
  (
911
916
  messages: ReadonlyArray<CrdtMessage>,
912
917
  lastTimestamp: Timestamp,
913
- options: { isMigrationFromVersion0?: boolean } = {},
914
918
  ): Result<void, SqliteError> => {
919
+ const ownerId = ownerIdToBinaryOwnerId(deps.ownerRowRef.get().id);
920
+
915
921
  for (const message of messages) {
916
- if (!options.isMigrationFromVersion0) {
917
- const apply1 = applyMessageToAppTable(deps)(message);
918
- if (!apply1.ok) return apply1;
919
- }
922
+ const result1 = applyMessageToAppTable(deps)(ownerId, message);
923
+ if (!result1.ok) return result1;
920
924
 
921
- const apply2 = applyMessageToTimestampAndHistoryTables(deps)(message);
922
- if (!apply2.ok) return apply2;
925
+ const result2 = applyMessageToTimestampAndHistoryTables(deps)(
926
+ ownerId,
927
+ message,
928
+ );
929
+ if (!result2.ok) return result2;
923
930
  }
924
931
 
925
932
  const timestamp = timestampToTimestampString(lastTimestamp);
@@ -933,8 +940,8 @@ const applyMessages =
933
940
  };
934
941
 
935
942
  const applyMessageToAppTable =
936
- (deps: SqliteDep) =>
937
- (message: CrdtMessage): Result<void, SqliteError> => {
943
+ (deps: SqliteDep & OwnerRowRefDep) =>
944
+ (ownerId: BinaryOwnerId, message: CrdtMessage): Result<void, SqliteError> => {
938
945
  const date = new Date(message.timestamp.millis).toISOString();
939
946
  const timestamp = timestampToBinaryTimestamp(message.timestamp);
940
947
 
@@ -942,28 +949,29 @@ const applyMessageToAppTable =
942
949
  const result = deps.sqlite.exec(sql.prepared`
943
950
  with
944
951
  lastTimestamp as (
945
- select timestamp
952
+ select "timestamp"
946
953
  from evolu_history
947
954
  where
948
- "row" = ${message.change.id}
949
- and "column" = ${column}
955
+ "ownerId" = ${ownerId}
950
956
  and "table" = ${message.change.table}
951
- order by timestamp desc
957
+ and "id" = ${message.change.id}
958
+ and "column" = ${column}
959
+ order by "timestamp" desc
952
960
  limit 1
953
961
  )
954
962
  insert into ${sql.identifier(message.change.table)}
955
963
  ("id", ${sql.identifier(column)}, createdAt, updatedAt)
956
964
  select ${message.change.id}, ${value}, ${date}, ${date}
957
965
  where
958
- (select timestamp from lastTimestamp) is null
959
- or (select timestamp from lastTimestamp) < ${timestamp}
966
+ (select "timestamp" from lastTimestamp) is null
967
+ or (select "timestamp" from lastTimestamp) < ${timestamp}
960
968
  on conflict ("id") do update
961
969
  set
962
970
  ${sql.identifier(column)} = ${value},
963
971
  updatedAt = ${date}
964
972
  where
965
- (select timestamp from lastTimestamp) is null
966
- or (select timestamp from lastTimestamp) < ${timestamp};
973
+ (select "timestamp" from lastTimestamp) is null
974
+ or (select "timestamp" from lastTimestamp) < ${timestamp};
967
975
  `);
968
976
 
969
977
  if (!result.ok) return result;
@@ -973,10 +981,9 @@ const applyMessageToAppTable =
973
981
  };
974
982
 
975
983
  export const applyMessageToTimestampAndHistoryTables =
976
- (deps: SqliteDep & RandomDep & OwnerRowRefDep & ClientStorageDep) =>
977
- (message: CrdtMessage): Result<void, SqliteError> => {
984
+ (deps: SqliteDep & ClientStorageDep) =>
985
+ (ownerId: BinaryOwnerId, message: CrdtMessage): Result<void, SqliteError> => {
978
986
  const timestamp = timestampToBinaryTimestamp(message.timestamp);
979
- const ownerId = ownerIdToBinaryOwnerId(deps.ownerRowRef.get().id);
980
987
  const id = idToBinaryId(message.change.id);
981
988
 
982
989
  const result = deps.storage.insertTimestamp(ownerId, timestamp);
@@ -985,9 +992,16 @@ export const applyMessageToTimestampAndHistoryTables =
985
992
  for (const [column, value] of Object.entries(message.change.values)) {
986
993
  const result = deps.sqlite.exec(sql.prepared`
987
994
  insert into evolu_history
988
- ("timestamp", "table", "row", "column", "value")
995
+ ("ownerId", "table", "id", "column", "value", "timestamp")
989
996
  values
990
- (${timestamp}, ${message.change.table}, ${id}, ${column}, ${value})
997
+ (
998
+ ${ownerId},
999
+ ${message.change.table},
1000
+ ${id},
1001
+ ${column},
1002
+ ${value},
1003
+ ${timestamp}
1004
+ )
991
1005
  on conflict do nothing;
992
1006
  `);
993
1007
  if (!result.ok) return result;
@@ -1063,11 +1077,11 @@ export const maybeMigrateToVersion0 =
1063
1077
  const messagesRows = deps.sqlite.exec<{
1064
1078
  timestamp: TimestampString;
1065
1079
  table: Base64Url256;
1066
- row: Id;
1080
+ id: Id;
1067
1081
  column: Base64Url256;
1068
1082
  value: SqliteValue;
1069
1083
  }>(sql`
1070
- select "timestamp", "table", "row", "column", "value" from evolu_message;
1084
+ select "timestamp", "table", "id", "column", "value" from evolu_message;
1071
1085
  `);
1072
1086
 
1073
1087
  if (!messagesRows.ok) return messagesRows;
@@ -1083,7 +1097,7 @@ export const maybeMigrateToVersion0 =
1083
1097
  const messages = messagesRows.value.rows.map((message) => ({
1084
1098
  timestamp: timestampStringToTimestamp(message.timestamp),
1085
1099
  change: {
1086
- id: message.row,
1100
+ id: message.id,
1087
1101
  table: message.table,
1088
1102
  values: { [message.column]: message.value },
1089
1103
  },
@@ -1231,16 +1245,16 @@ const createClientStorage =
1231
1245
  return true;
1232
1246
  },
1233
1247
 
1234
- readDbChange: (_ownerId, timestamp) => {
1248
+ readDbChange: (ownerId, timestamp) => {
1235
1249
  const result = deps.sqlite.exec<{
1236
1250
  table: Base64Url256;
1237
- row: BinaryId;
1251
+ id: BinaryId;
1238
1252
  column: Base64Url256;
1239
1253
  value: SqliteValue;
1240
1254
  }>(sql`
1241
- select "table", "row", "column", "value"
1255
+ select "table", "id", "column", "value"
1242
1256
  from evolu_history
1243
- where "timestamp" = ${timestamp};
1257
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
1244
1258
  `);
1245
1259
  if (!result.ok) {
1246
1260
  deps.postMessage({ type: "onError", error: result.error });
@@ -1250,18 +1264,18 @@ const createClientStorage =
1250
1264
  const { rows } = result.value;
1251
1265
  assert(rows.length > 0, "Rows must not be empty");
1252
1266
 
1253
- const { table, row } = rows[0];
1267
+ const { table, id } = rows[0];
1254
1268
  const values: Record<string, SqliteValue> = {};
1255
1269
 
1256
1270
  for (const r of rows) {
1257
1271
  assert(r.table === table, "All rows must have the same table");
1258
- assert(eqArrayNumber(r.row, row), "All rows must have the same Id");
1272
+ assert(eqArrayNumber(r.id, id), "All rows must have the same Id");
1259
1273
  values[r.column] = r.value;
1260
1274
  }
1261
1275
 
1262
1276
  const change: DbChange = {
1263
1277
  table: rows[0].table,
1264
- id: binaryIdToId(rows[0].row),
1278
+ id: binaryIdToId(rows[0].id),
1265
1279
  values,
1266
1280
  };
1267
1281