@evolu/common 8.15.0 → 8.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Console.d.ts +24 -46
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +34 -29
- package/dist/src/Error.d.ts +10 -6
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +52 -20
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +16 -12
- package/dist/src/Task.d.ts +3 -1
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +15 -13
- package/dist/src/Worker.d.ts +3 -0
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +6 -1
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +7 -7
- package/dist/src/local-first/Evolu.d.ts +4 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +3 -0
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +9 -5
- package/dist/src/local-first/Shared.d.ts +55 -39
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +264 -320
- package/package.json +1 -1
- package/src/Console.test.ts +75 -247
- package/src/Console.ts +68 -110
- package/src/Error.test.ts +85 -0
- package/src/Error.ts +48 -22
- package/src/Sqlite.ts +19 -11
- package/src/Task.test.ts +46 -0
- package/src/Task.ts +17 -13
- package/src/Worker.ts +6 -1
- package/src/local-first/Db.ts +12 -8
- package/src/local-first/Evolu.ts +4 -0
- package/src/local-first/Schema.test.ts +80 -1
- package/src/local-first/Schema.ts +15 -5
- package/src/local-first/Shared.test.ts +414 -39
- package/src/local-first/Shared.ts +386 -440
package/src/Worker.ts
CHANGED
|
@@ -656,6 +656,9 @@ export const testCreateMessagePort: CreateMessagePort = <Input, Output = never>(
|
|
|
656
656
|
/**
|
|
657
657
|
* Creates an in-memory {@link BroadcastChannel} for testing.
|
|
658
658
|
*
|
|
659
|
+
* Like a native channel, it structured-clones each message, so posting a value
|
|
660
|
+
* that cannot be cloned throws.
|
|
661
|
+
*
|
|
659
662
|
* @group Testing
|
|
660
663
|
*/
|
|
661
664
|
export const testCreateBroadcastChannel = <Input, Output = Input>(
|
|
@@ -667,7 +670,9 @@ export const testCreateBroadcastChannel = <Input, Output = Input>(
|
|
|
667
670
|
const disposables = disposer.move();
|
|
668
671
|
|
|
669
672
|
return {
|
|
670
|
-
postMessage:
|
|
673
|
+
postMessage: (message) => {
|
|
674
|
+
channel.postMessage(structuredClone(message));
|
|
675
|
+
},
|
|
671
676
|
get onMessage() {
|
|
672
677
|
return channel.onMessage;
|
|
673
678
|
},
|
package/src/local-first/Db.ts
CHANGED
|
@@ -137,7 +137,10 @@ import type {
|
|
|
137
137
|
EvoluInput,
|
|
138
138
|
SharedWorkerId,
|
|
139
139
|
} from "./Shared.ts";
|
|
140
|
-
import {
|
|
140
|
+
import {
|
|
141
|
+
consoleEntryOrErrorBroadcastChannelName,
|
|
142
|
+
postConsoleEntry,
|
|
143
|
+
} from "./Shared.ts";
|
|
141
144
|
import {
|
|
142
145
|
createBaseSqliteStorage,
|
|
143
146
|
createBaseSqliteStorageTables,
|
|
@@ -229,6 +232,13 @@ export const startDbWorker =
|
|
|
229
232
|
self.onMessage = (message) => resolve(ok(message));
|
|
230
233
|
}),
|
|
231
234
|
);
|
|
235
|
+
// Tabs report the errors this worker logs as an UnknownError, so it
|
|
236
|
+
// forwards them even for a silent app, whose tabs do not print them.
|
|
237
|
+
deps.console.setLevel(
|
|
238
|
+
initMessage.consoleLevel === "silent"
|
|
239
|
+
? "error"
|
|
240
|
+
: initMessage.consoleLevel,
|
|
241
|
+
);
|
|
232
242
|
|
|
233
243
|
const port = disposer.use(
|
|
234
244
|
deps.createMessagePort<DbWorkerOutput, DbWorkerInput>(initMessage.port),
|
|
@@ -242,11 +252,7 @@ export const startDbWorker =
|
|
|
242
252
|
disposer.defer(
|
|
243
253
|
deps.consoleStoreOutputEntry.subscribe(() => {
|
|
244
254
|
const entry = deps.consoleStoreOutputEntry.get();
|
|
245
|
-
if (entry)
|
|
246
|
-
consoleEntryOrErrorBroadcastChannel.postMessage({
|
|
247
|
-
type: "ConsoleEntry",
|
|
248
|
-
entry,
|
|
249
|
-
});
|
|
255
|
+
if (entry) postConsoleEntry(consoleEntryOrErrorBroadcastChannel, entry);
|
|
250
256
|
}),
|
|
251
257
|
);
|
|
252
258
|
|
|
@@ -310,7 +316,6 @@ export const startDbWorker =
|
|
|
310
316
|
// waits on. The tab leader lock is unaffected.
|
|
311
317
|
port.postMessage({
|
|
312
318
|
type: "LeaderRefused",
|
|
313
|
-
name: initMessage.name,
|
|
314
319
|
error: startup.error,
|
|
315
320
|
});
|
|
316
321
|
return ok();
|
|
@@ -322,7 +327,6 @@ export const startDbWorker =
|
|
|
322
327
|
|
|
323
328
|
port.postMessage({
|
|
324
329
|
type: "LeaderAcquired",
|
|
325
|
-
name: initMessage.name,
|
|
326
330
|
clock: initialClock,
|
|
327
331
|
});
|
|
328
332
|
|
package/src/local-first/Evolu.ts
CHANGED
|
@@ -195,6 +195,10 @@ export interface EvoluConfig {
|
|
|
195
195
|
/**
|
|
196
196
|
* Use the `indexes` option to define SQLite indexes.
|
|
197
197
|
*
|
|
198
|
+
* Unique indexes are rejected with an assertion because concurrent offline
|
|
199
|
+
* writes can violate uniqueness during synchronization. See
|
|
200
|
+
* [Uniqueness](https://www.evolu.dev/docs/schema#uniqueness).
|
|
201
|
+
*
|
|
198
202
|
* Table and column names are not typed because Kysely doesn't support it.
|
|
199
203
|
*
|
|
200
204
|
* https://medium.com/@JasonWyatt/squeezing-performance-from-sqlite-indexes-indexes-c4e175f3c346
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
assertErr,
|
|
5
5
|
assertOk,
|
|
6
6
|
assertSame,
|
|
7
|
+
assertThrowsInstanceOf,
|
|
7
8
|
assertType,
|
|
8
9
|
createEvolu,
|
|
9
10
|
createQueryBuilder,
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
type TimestampBytes,
|
|
28
29
|
} from "../index.ts";
|
|
29
30
|
import type { EvoluPlatformDeps } from "./Evolu.ts";
|
|
30
|
-
import type
|
|
31
|
+
import { evoluSchemaToSqliteSchema, type ValidateSchema } from "./Schema.ts";
|
|
31
32
|
|
|
32
33
|
test("history queries select and filter by ownerId bytes", () => {
|
|
33
34
|
const createQuery = createQueryBuilder(testEvoluSchema);
|
|
@@ -44,6 +45,84 @@ test("history queries select and filter by ownerId bytes", () => {
|
|
|
44
45
|
>();
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
test("evoluSchemaToSqliteSchema compiles ordinary indexes", () => {
|
|
49
|
+
const sqliteSchema = evoluSchemaToSqliteSchema(testEvoluSchema, (create) => [
|
|
50
|
+
create("todoTitle").on("todo").column("title"),
|
|
51
|
+
create("todoProjectIdTitle").on("todo").columns(["projectId", "title"]),
|
|
52
|
+
create("todoTitleNotNull")
|
|
53
|
+
.on("todo")
|
|
54
|
+
.column("title")
|
|
55
|
+
.where("title", "is not", null),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
assertEqual(sqliteSchema.indexes, [
|
|
59
|
+
{
|
|
60
|
+
name: "todoTitle",
|
|
61
|
+
sql: 'create index "todoTitle" on "todo" ("title")',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "todoProjectIdTitle",
|
|
65
|
+
sql: 'create index "todoProjectIdTitle" on "todo" ("projectId", "title")',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "todoTitleNotNull",
|
|
69
|
+
sql: 'create index "todoTitleNotNull" on "todo" ("title") where "title" is not null',
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("evoluSchemaToSqliteSchema rejects unique indexes", () => {
|
|
75
|
+
const error = assertThrowsInstanceOf(
|
|
76
|
+
() =>
|
|
77
|
+
evoluSchemaToSqliteSchema(testEvoluSchema, (create) => [
|
|
78
|
+
create("todoUniqueTitle").on("todo").column("title").unique(),
|
|
79
|
+
]),
|
|
80
|
+
Error,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
assertEqual(
|
|
84
|
+
error.message,
|
|
85
|
+
"Unique indexes are not supported because they can prevent synchronization.",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("evoluSchemaToSqliteSchema rejects composite unique indexes", () => {
|
|
90
|
+
const error = assertThrowsInstanceOf(
|
|
91
|
+
() =>
|
|
92
|
+
evoluSchemaToSqliteSchema(testEvoluSchema, (create) => [
|
|
93
|
+
create("todoUniqueProjectIdTitle")
|
|
94
|
+
.on("todo")
|
|
95
|
+
.columns(["projectId", "title"])
|
|
96
|
+
.unique(),
|
|
97
|
+
]),
|
|
98
|
+
Error,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
assertEqual(
|
|
102
|
+
error.message,
|
|
103
|
+
"Unique indexes are not supported because they can prevent synchronization.",
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("evoluSchemaToSqliteSchema rejects partial unique indexes", () => {
|
|
108
|
+
const error = assertThrowsInstanceOf(
|
|
109
|
+
() =>
|
|
110
|
+
evoluSchemaToSqliteSchema(testEvoluSchema, (create) => [
|
|
111
|
+
create("todoUniqueTitleNotNull")
|
|
112
|
+
.on("todo")
|
|
113
|
+
.column("title")
|
|
114
|
+
.where("title", "is not", null)
|
|
115
|
+
.unique(),
|
|
116
|
+
]),
|
|
117
|
+
Error,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
assertEqual(
|
|
121
|
+
error.message,
|
|
122
|
+
"Unique indexes are not supported because they can prevent synchronization.",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
47
126
|
test("testEvoluSchema supports project-linked and independent todos", () => {
|
|
48
127
|
assertType<TestEvoluSchema, typeof testEvoluSchema>();
|
|
49
128
|
assertType<ValidateSchema<TestEvoluSchema>, TestEvoluSchema>();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Kysely from "kysely";
|
|
8
8
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
9
|
-
import { assertNonNullable } from "../Assert.ts";
|
|
9
|
+
import { assert, assertNonNullable } from "../Assert.ts";
|
|
10
10
|
import { getOwnProp, mapObject, type ReadonlyRecord } from "../Object.ts";
|
|
11
11
|
import {
|
|
12
12
|
eqSqliteIndex,
|
|
@@ -299,6 +299,9 @@ export type ValidateSchema<S extends EvoluSchema> =
|
|
|
299
299
|
/**
|
|
300
300
|
* Defines SQLite indexes with Kysely's index builder.
|
|
301
301
|
*
|
|
302
|
+
* Unique indexes are not supported because concurrent offline writes can
|
|
303
|
+
* violate uniqueness during synchronization.
|
|
304
|
+
*
|
|
302
305
|
* @group SQLite
|
|
303
306
|
*/
|
|
304
307
|
export type IndexesConfig = (
|
|
@@ -800,10 +803,17 @@ export const evoluSchemaToSqliteSchema = <S extends EvoluSchema>(
|
|
|
800
803
|
);
|
|
801
804
|
|
|
802
805
|
const indexes = indexesConfig
|
|
803
|
-
? indexesConfig(createIndex).map((index): SqliteIndex =>
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
806
|
+
? indexesConfig(createIndex).map((index): SqliteIndex => {
|
|
807
|
+
const node = index.toOperationNode();
|
|
808
|
+
assert(
|
|
809
|
+
!node.unique,
|
|
810
|
+
"Unique indexes are not supported because they can prevent synchronization.",
|
|
811
|
+
);
|
|
812
|
+
return {
|
|
813
|
+
name: node.name.name,
|
|
814
|
+
sql: index.compile().sql,
|
|
815
|
+
};
|
|
816
|
+
})
|
|
807
817
|
: [];
|
|
808
818
|
|
|
809
819
|
return { tables, indexes };
|