@evolu/common 3.1.8 → 4.0.1
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/Config.d.ts +6 -0
- package/dist/src/Config.d.ts.map +1 -1
- package/dist/src/Config.js +1 -0
- package/dist/src/Crdt.d.ts +2 -2
- package/dist/src/Crdt.d.ts.map +1 -1
- package/dist/src/Crdt.js +2 -2
- package/dist/src/Crypto.d.ts +5 -4
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +2 -2
- package/dist/src/Db.d.ts +2 -2
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +5 -7
- package/dist/src/DbWorker.d.ts +8 -8
- package/dist/src/DbWorker.d.ts.map +1 -1
- package/dist/src/DbWorker.js +44 -28
- package/dist/src/Evolu.d.ts +4 -5
- package/dist/src/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu.js +10 -13
- package/dist/src/OnCompletes.d.ts +3 -3
- package/dist/src/OnCompletes.d.ts.map +1 -1
- package/dist/src/OnCompletes.js +3 -3
- package/dist/src/Platform.d.ts +3 -1
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +1 -0
- package/dist/src/Sql.d.ts +10 -10
- package/dist/src/Sql.d.ts.map +1 -1
- package/dist/src/Sql.js +48 -47
- package/dist/src/Sqlite.d.ts +3 -2
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +11 -0
- package/dist/src/SyncWorker.d.ts +3 -4
- package/dist/src/SyncWorker.d.ts.map +1 -1
- package/dist/src/SyncWorker.js +1 -1
- package/dist/src/Types.d.ts +13 -0
- package/dist/src/Types.d.ts.map +1 -0
- package/dist/src/Types.js +1 -0
- package/package.json +5 -5
- package/src/Config.ts +8 -0
- package/src/Crdt.ts +2 -2
- package/src/Crypto.ts +11 -7
- package/src/Db.ts +9 -11
- package/src/DbWorker.ts +54 -44
- package/src/Evolu.ts +23 -18
- package/src/OnCompletes.ts +4 -4
- package/src/Platform.ts +6 -5
- package/src/Sql.ts +52 -49
- package/src/Sqlite.ts +18 -2
- package/src/SyncWorker.ts +4 -6
- package/src/Types.ts +11 -0
package/src/DbWorker.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { hexToBytes } from "@noble/ciphers/utils";
|
|
2
1
|
import * as Context from "effect/Context";
|
|
3
2
|
import * as Effect from "effect/Effect";
|
|
4
3
|
import * as Function from "effect/Function";
|
|
@@ -11,6 +10,7 @@ import * as ReadonlyRecord from "effect/ReadonlyRecord";
|
|
|
11
10
|
import { Config, ConfigLive } from "./Config.js";
|
|
12
11
|
import {
|
|
13
12
|
MerkleTree,
|
|
13
|
+
Millis,
|
|
14
14
|
Time,
|
|
15
15
|
TimeLive,
|
|
16
16
|
Timestamp,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
timestampToString,
|
|
29
29
|
unsafeTimestampFromString,
|
|
30
30
|
} from "./Crdt.js";
|
|
31
|
-
import { Bip39, Mnemonic,
|
|
31
|
+
import { Bip39, Mnemonic, NanoIdGenerator } from "./Crypto.js";
|
|
32
32
|
import {
|
|
33
33
|
Queries,
|
|
34
34
|
Query,
|
|
@@ -44,9 +44,10 @@ import {
|
|
|
44
44
|
} from "./Db.js";
|
|
45
45
|
import { QueryPatches, makePatches } from "./Diff.js";
|
|
46
46
|
import { EvoluError, makeUnexpectedError } from "./ErrorStore.js";
|
|
47
|
-
import { Id,
|
|
47
|
+
import { Id, cast } from "./Model.js";
|
|
48
48
|
import { OnCompleteId } from "./OnCompletes.js";
|
|
49
49
|
import { Owner, OwnerId } from "./Owner.js";
|
|
50
|
+
import { DbWorkerLock } from "./Platform.js";
|
|
50
51
|
import * as Sql from "./Sql.js";
|
|
51
52
|
import { Sqlite, Value } from "./Sqlite.js";
|
|
52
53
|
import {
|
|
@@ -58,12 +59,9 @@ import {
|
|
|
58
59
|
SyncWorkerOutputSyncResponse,
|
|
59
60
|
SyncWorkerPostMessage,
|
|
60
61
|
} from "./SyncWorker.js";
|
|
62
|
+
import { Messaging } from "./Types.js";
|
|
61
63
|
|
|
62
|
-
export interface DbWorker {
|
|
63
|
-
readonly postMessage: (input: DbWorkerInput) => void;
|
|
64
|
-
onMessage: (output: DbWorkerOutput) => void;
|
|
65
|
-
}
|
|
66
|
-
|
|
64
|
+
export interface DbWorker extends Messaging<DbWorkerInput, DbWorkerOutput> {}
|
|
67
65
|
export const DbWorker = Context.GenericTag<DbWorker>("@services/DbWorker");
|
|
68
66
|
|
|
69
67
|
export type DbWorkerInput =
|
|
@@ -157,7 +155,6 @@ export interface Mutation {
|
|
|
157
155
|
Value | Date | boolean | undefined
|
|
158
156
|
>;
|
|
159
157
|
readonly isInsert: boolean;
|
|
160
|
-
readonly now: SqliteDate;
|
|
161
158
|
readonly onCompleteId: OnCompleteId | null;
|
|
162
159
|
}
|
|
163
160
|
|
|
@@ -170,8 +167,7 @@ const init = Effect.gen(function* (_) {
|
|
|
170
167
|
({ rows: [row] }): Owner => ({
|
|
171
168
|
id: row.id as OwnerId,
|
|
172
169
|
mnemonic: row.mnemonic as Mnemonic,
|
|
173
|
-
|
|
174
|
-
encryptionKey: hexToBytes(row.encryptionKey as string),
|
|
170
|
+
encryptionKey: row.encryptionKey as Uint8Array,
|
|
175
171
|
}),
|
|
176
172
|
),
|
|
177
173
|
someDefectToNoSuchTableOrColumnError,
|
|
@@ -222,9 +218,8 @@ const readTimestampAndMerkleTree = Sqlite.pipe(
|
|
|
222
218
|
Effect.flatMap((sqlite) =>
|
|
223
219
|
sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree),
|
|
224
220
|
),
|
|
225
|
-
Effect.map((result) => result.rows),
|
|
226
221
|
Effect.map(
|
|
227
|
-
([{ timestamp, merkleTree }]): TimestampAndMerkleTree => ({
|
|
222
|
+
({ rows: [{ timestamp, merkleTree }] }): TimestampAndMerkleTree => ({
|
|
228
223
|
timestamp: unsafeTimestampFromString(timestamp as TimestampString),
|
|
229
224
|
merkleTree: merkleTree as MerkleTree,
|
|
230
225
|
}),
|
|
@@ -236,9 +231,10 @@ export const mutationsToNewMessages = (
|
|
|
236
231
|
): ReadonlyArray<NewMessage> =>
|
|
237
232
|
pipe(
|
|
238
233
|
mutations,
|
|
239
|
-
ReadonlyArray.map(({ id, isInsert,
|
|
234
|
+
ReadonlyArray.map(({ id, isInsert, table, values }) =>
|
|
240
235
|
pipe(
|
|
241
236
|
Object.entries(values),
|
|
237
|
+
// Filter values.
|
|
242
238
|
ReadonlyArray.filterMap(([key, value]) =>
|
|
243
239
|
// The value can be undefined if exactOptionalPropertyTypes isn't true.
|
|
244
240
|
// Don't insert nulls because null is the default value.
|
|
@@ -246,6 +242,7 @@ export const mutationsToNewMessages = (
|
|
|
246
242
|
? Option.none()
|
|
247
243
|
: Option.some([key, value] as const),
|
|
248
244
|
),
|
|
245
|
+
// Cast values.
|
|
249
246
|
ReadonlyArray.map(
|
|
250
247
|
([key, value]) =>
|
|
251
248
|
[
|
|
@@ -257,7 +254,6 @@ export const mutationsToNewMessages = (
|
|
|
257
254
|
: value,
|
|
258
255
|
] as const,
|
|
259
256
|
),
|
|
260
|
-
ReadonlyArray.append([isInsert ? "createdAt" : "updatedAt", now]),
|
|
261
257
|
ReadonlyArray.map(
|
|
262
258
|
([key, value]): NewMessage => ({
|
|
263
259
|
table,
|
|
@@ -282,7 +278,7 @@ const ensureSchemaByNewMessages = (
|
|
|
282
278
|
if (table == null) {
|
|
283
279
|
tablesMap.set(message.table, {
|
|
284
280
|
name: message.table,
|
|
285
|
-
columns: [message.column],
|
|
281
|
+
columns: [message.column, "createdAt", "updatedAt"],
|
|
286
282
|
});
|
|
287
283
|
return;
|
|
288
284
|
}
|
|
@@ -298,13 +294,21 @@ const ensureSchemaByNewMessages = (
|
|
|
298
294
|
export const upsertValueIntoTableRowColumn = (
|
|
299
295
|
message: NewMessage,
|
|
300
296
|
messages: ReadonlyArray<NewMessage>,
|
|
297
|
+
millis: Millis,
|
|
301
298
|
): Effect.Effect<void, never, Sqlite> =>
|
|
302
299
|
Effect.gen(function* (_) {
|
|
303
300
|
const sqlite = yield* _(Sqlite);
|
|
304
|
-
|
|
301
|
+
const createdAtOrUpdatedAt = cast(new Date(millis));
|
|
305
302
|
const insert = sqlite.exec({
|
|
306
303
|
sql: Sql.upsertValueIntoTableRowColumn(message.table, message.column),
|
|
307
|
-
parameters: [
|
|
304
|
+
parameters: [
|
|
305
|
+
message.row,
|
|
306
|
+
message.value,
|
|
307
|
+
createdAtOrUpdatedAt,
|
|
308
|
+
createdAtOrUpdatedAt,
|
|
309
|
+
message.value,
|
|
310
|
+
createdAtOrUpdatedAt,
|
|
311
|
+
],
|
|
308
312
|
});
|
|
309
313
|
|
|
310
314
|
yield* _(
|
|
@@ -340,7 +344,8 @@ const applyMessages = ({
|
|
|
340
344
|
);
|
|
341
345
|
|
|
342
346
|
if (timestamp == null || timestamp < message.timestamp) {
|
|
343
|
-
|
|
347
|
+
const { millis } = unsafeTimestampFromString(message.timestamp);
|
|
348
|
+
yield* _(upsertValueIntoTableRowColumn(message, messages, millis));
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
if (timestamp == null || timestamp !== message.timestamp) {
|
|
@@ -406,11 +411,11 @@ const mutate = ({
|
|
|
406
411
|
),
|
|
407
412
|
).map(mutationsToNewMessages);
|
|
408
413
|
|
|
409
|
-
yield* _(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
+
const time = yield* _(Time);
|
|
415
|
+
for (const messageToUpsert of toUpsert) {
|
|
416
|
+
const now = yield* _(time.now);
|
|
417
|
+
yield* _(upsertValueIntoTableRowColumn(messageToUpsert, toUpsert, now));
|
|
418
|
+
}
|
|
414
419
|
|
|
415
420
|
const { exec } = yield* _(Sqlite);
|
|
416
421
|
yield* _(
|
|
@@ -537,8 +542,8 @@ const sync = ({
|
|
|
537
542
|
> =>
|
|
538
543
|
Effect.gen(function* (_) {
|
|
539
544
|
if (queries.length > 0) yield* _(query({ queries }));
|
|
540
|
-
|
|
541
|
-
(
|
|
545
|
+
const syncWorkerPostMessage = yield* _(SyncWorkerPostMessage);
|
|
546
|
+
syncWorkerPostMessage({
|
|
542
547
|
_tag: "sync",
|
|
543
548
|
...(yield* _(readTimestampAndMerkleTree)),
|
|
544
549
|
syncUrl: (yield* _(Config)).syncUrl,
|
|
@@ -550,7 +555,11 @@ const sync = ({
|
|
|
550
555
|
|
|
551
556
|
const reset = (
|
|
552
557
|
input: DbWorkerInputReset,
|
|
553
|
-
): Effect.Effect<
|
|
558
|
+
): Effect.Effect<
|
|
559
|
+
void,
|
|
560
|
+
never,
|
|
561
|
+
Sqlite | Bip39 | NanoIdGenerator | DbWorkerOnMessage
|
|
562
|
+
> =>
|
|
554
563
|
Effect.gen(function* (_) {
|
|
555
564
|
const sqlite = yield* _(Sqlite);
|
|
556
565
|
|
|
@@ -575,7 +584,7 @@ const reset = (
|
|
|
575
584
|
onMessage({ _tag: "onResetOrRestore" });
|
|
576
585
|
});
|
|
577
586
|
|
|
578
|
-
export const
|
|
587
|
+
export const DbWorkerCommonLive = Layer.effect(
|
|
579
588
|
DbWorker,
|
|
580
589
|
Effect.gen(function* (_) {
|
|
581
590
|
const syncWorker = yield* _(SyncWorker);
|
|
@@ -591,7 +600,7 @@ export const DbWorkerLive = Layer.effect(
|
|
|
591
600
|
onError(output).pipe(Effect.runSync);
|
|
592
601
|
break;
|
|
593
602
|
case "SyncWorkerOutputSyncResponse":
|
|
594
|
-
postMessage(output);
|
|
603
|
+
dbWorker.postMessage(output);
|
|
595
604
|
break;
|
|
596
605
|
default:
|
|
597
606
|
dbWorker.onMessage({ _tag: "onSyncState", state: output });
|
|
@@ -601,7 +610,7 @@ export const DbWorkerLive = Layer.effect(
|
|
|
601
610
|
const runContext = Context.empty().pipe(
|
|
602
611
|
Context.add(Sqlite, yield* _(Sqlite)),
|
|
603
612
|
Context.add(Bip39, yield* _(Bip39)),
|
|
604
|
-
Context.add(
|
|
613
|
+
Context.add(NanoIdGenerator, yield* _(NanoIdGenerator)),
|
|
605
614
|
Context.add(DbWorkerOnMessage, (output) => {
|
|
606
615
|
dbWorker.onMessage(output);
|
|
607
616
|
}),
|
|
@@ -611,7 +620,7 @@ export const DbWorkerLive = Layer.effect(
|
|
|
611
620
|
effect: Effect.Effect<
|
|
612
621
|
void,
|
|
613
622
|
EvoluError,
|
|
614
|
-
Sqlite | Bip39 |
|
|
623
|
+
Sqlite | Bip39 | NanoIdGenerator | DbWorkerOnMessage
|
|
615
624
|
>,
|
|
616
625
|
): Promise<void> =>
|
|
617
626
|
effect.pipe(
|
|
@@ -622,15 +631,18 @@ export const DbWorkerLive = Layer.effect(
|
|
|
622
631
|
Effect.runPromise,
|
|
623
632
|
);
|
|
624
633
|
|
|
625
|
-
type
|
|
634
|
+
type HandleInput = (input: DbWorkerInput) => Promise<void>;
|
|
626
635
|
|
|
627
|
-
|
|
628
|
-
const
|
|
636
|
+
/** If init fails, we have to allow reset at least. */
|
|
637
|
+
const handleInputForInitFail: HandleInput = (input): Promise<void> => {
|
|
629
638
|
if (input._tag !== "reset") return Promise.resolve(undefined);
|
|
630
639
|
return reset(input).pipe(run);
|
|
631
640
|
};
|
|
632
641
|
|
|
633
|
-
const
|
|
642
|
+
const makeHandleInputForInitSuccess = (
|
|
643
|
+
config: Config,
|
|
644
|
+
owner: Owner,
|
|
645
|
+
): HandleInput => {
|
|
634
646
|
let skipAllBecauseOfReset = false;
|
|
635
647
|
|
|
636
648
|
const layer = Layer.mergeAll(
|
|
@@ -663,27 +675,25 @@ export const DbWorkerLive = Layer.effect(
|
|
|
663
675
|
};
|
|
664
676
|
};
|
|
665
677
|
|
|
666
|
-
let
|
|
678
|
+
let handleInput: HandleInput = (input) => {
|
|
667
679
|
if (input._tag !== "init")
|
|
668
680
|
return run(makeUnexpectedError(new Error("init must be called first")));
|
|
669
|
-
|
|
681
|
+
handleInput = handleInputForInitFail;
|
|
670
682
|
return init.pipe(
|
|
671
683
|
Effect.map((owner) => {
|
|
672
684
|
dbWorker.onMessage({ _tag: "onOwner", owner });
|
|
673
|
-
|
|
685
|
+
handleInput = makeHandleInputForInitSuccess(input.config, owner);
|
|
674
686
|
}),
|
|
675
687
|
run,
|
|
676
688
|
);
|
|
677
689
|
};
|
|
678
690
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const postMessage: DbWorker["postMessage"] = (input) => {
|
|
682
|
-
messageQueue = messageQueue.then(() => write(input));
|
|
683
|
-
};
|
|
691
|
+
const dbWorkerLock = yield* _(DbWorkerLock);
|
|
684
692
|
|
|
685
693
|
const dbWorker: DbWorker = {
|
|
686
|
-
postMessage
|
|
694
|
+
postMessage: (input) => {
|
|
695
|
+
dbWorkerLock(() => handleInput(input));
|
|
696
|
+
},
|
|
687
697
|
onMessage: Function.constVoid,
|
|
688
698
|
};
|
|
689
699
|
|
package/src/Evolu.ts
CHANGED
|
@@ -10,8 +10,7 @@ import * as Number from "effect/Number";
|
|
|
10
10
|
import * as ReadonlyArray from "effect/ReadonlyArray";
|
|
11
11
|
import * as Kysely from "kysely";
|
|
12
12
|
import { Config, ConfigLive } from "./Config.js";
|
|
13
|
-
import {
|
|
14
|
-
import { Mnemonic, NanoId, NanoIdLive } from "./Crypto.js";
|
|
13
|
+
import { Mnemonic, NanoIdGenerator, NanoIdGeneratorLive } from "./Crypto.js";
|
|
15
14
|
import {
|
|
16
15
|
DatabaseSchema,
|
|
17
16
|
Queries,
|
|
@@ -29,11 +28,11 @@ import {
|
|
|
29
28
|
import { DbWorker, DbWorkerOutputOnQuery, Mutation } from "./DbWorker.js";
|
|
30
29
|
import { applyPatches } from "./Diff.js";
|
|
31
30
|
import { EvoluError, makeErrorStore } from "./ErrorStore.js";
|
|
32
|
-
import { SqliteBoolean, SqliteDate
|
|
31
|
+
import { SqliteBoolean, SqliteDate } from "./Model.js";
|
|
33
32
|
import { OnCompletes, OnCompletesLive } from "./OnCompletes.js";
|
|
34
33
|
import { Owner } from "./Owner.js";
|
|
35
34
|
import { AppState, FlushSync, PlatformName } from "./Platform.js";
|
|
36
|
-
import { SqliteQuery } from "./Sqlite.js";
|
|
35
|
+
import { SqliteQuery, isSqlMutation } from "./Sqlite.js";
|
|
37
36
|
import { Store, Unsubscribe, makeStore } from "./Store.js";
|
|
38
37
|
import { SyncState } from "./SyncWorker.js";
|
|
39
38
|
|
|
@@ -319,17 +318,22 @@ type CreateQuery<S extends DatabaseSchema> = <R extends Row>(
|
|
|
319
318
|
) => Query<R>;
|
|
320
319
|
|
|
321
320
|
type QueryCallback<S extends DatabaseSchema, R extends Row> = (
|
|
322
|
-
db: Pick<
|
|
321
|
+
db: Pick<
|
|
322
|
+
Kysely.Kysely<QuerySchema<S>>,
|
|
323
|
+
"selectFrom" | "fn" | "with" | "withRecursive"
|
|
324
|
+
>,
|
|
323
325
|
) => Kysely.SelectQueryBuilder<any, any, R>;
|
|
324
326
|
|
|
325
327
|
type QuerySchema<S extends DatabaseSchema> = {
|
|
326
|
-
readonly [Table in keyof S]:
|
|
328
|
+
readonly [Table in keyof S]: NullableExceptForIdAndAutomaticColumns<{
|
|
327
329
|
readonly [Column in keyof S[Table]]: S[Table][Column];
|
|
328
330
|
}>;
|
|
329
331
|
};
|
|
330
332
|
|
|
331
|
-
type
|
|
332
|
-
readonly [K in keyof T]: K extends "id"
|
|
333
|
+
type NullableExceptForIdAndAutomaticColumns<T> = {
|
|
334
|
+
readonly [K in keyof T]: K extends "id" | "createdAt" | "updatedAt"
|
|
335
|
+
? T[K]
|
|
336
|
+
: T[K] | null;
|
|
333
337
|
};
|
|
334
338
|
|
|
335
339
|
const kysely = new Kysely.Kysely<QuerySchema<DatabaseSchema>>({
|
|
@@ -349,10 +353,14 @@ export const makeCreateQuery =
|
|
|
349
353
|
<R extends Row>(queryCallback: QueryCallback<S, R>) =>
|
|
350
354
|
pipe(
|
|
351
355
|
queryCallback(kysely as Kysely.Kysely<QuerySchema<S>>).compile(),
|
|
352
|
-
({ sql, parameters }): SqliteQuery =>
|
|
353
|
-
sql
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
({ sql, parameters }): SqliteQuery => {
|
|
357
|
+
if (isSqlMutation(sql))
|
|
358
|
+
throw new Error(
|
|
359
|
+
"SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu mutation API.",
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
return { sql, parameters: parameters as SqliteQuery["parameters"] };
|
|
363
|
+
},
|
|
356
364
|
(query) => serializeQuery<R>(query),
|
|
357
365
|
);
|
|
358
366
|
|
|
@@ -643,9 +651,8 @@ type Castable<T> = {
|
|
|
643
651
|
const MutateLive = Layer.effect(
|
|
644
652
|
Mutate,
|
|
645
653
|
Effect.gen(function* (_) {
|
|
646
|
-
const nanoid = yield* _(
|
|
654
|
+
const { nanoid } = yield* _(NanoIdGenerator);
|
|
647
655
|
const onCompletes = yield* _(OnCompletes);
|
|
648
|
-
const time = yield* _(Time);
|
|
649
656
|
const subscribedQueries = yield* _(SubscribedQueries);
|
|
650
657
|
const loadingPromises = yield* _(LoadingPromises);
|
|
651
658
|
const dbWorker = yield* _(DbWorker);
|
|
@@ -653,7 +660,7 @@ const MutateLive = Layer.effect(
|
|
|
653
660
|
|
|
654
661
|
return Mutate.of((table, { id, ...values }, onComplete) => {
|
|
655
662
|
const isInsert = id == null;
|
|
656
|
-
if (isInsert) id = Effect.runSync(nanoid
|
|
663
|
+
if (isInsert) id = Effect.runSync(nanoid) as never;
|
|
657
664
|
|
|
658
665
|
const onCompleteId = onComplete
|
|
659
666
|
? onCompletes.add(onComplete).pipe(Effect.runSync)
|
|
@@ -666,7 +673,6 @@ const MutateLive = Layer.effect(
|
|
|
666
673
|
id,
|
|
667
674
|
values,
|
|
668
675
|
isInsert,
|
|
669
|
-
now: cast(new Date(Effect.runSync(time.now))),
|
|
670
676
|
onCompleteId,
|
|
671
677
|
},
|
|
672
678
|
];
|
|
@@ -785,9 +791,8 @@ const EvoluCommon = Layer.effect(
|
|
|
785
791
|
Layer.provide(Layer.merge(RowsStoreLive, OnCompletesLive)),
|
|
786
792
|
);
|
|
787
793
|
|
|
788
|
-
/** EvoluCommonLive has only platform independent side-effects. */
|
|
789
794
|
export const EvoluCommonLive = EvoluCommon.pipe(
|
|
790
|
-
Layer.provide(
|
|
795
|
+
Layer.provide(NanoIdGeneratorLive),
|
|
791
796
|
);
|
|
792
797
|
|
|
793
798
|
/**
|
package/src/OnCompletes.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as Effect from "effect/Effect";
|
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
5
|
import * as Option from "effect/Option";
|
|
6
6
|
import * as ReadonlyArray from "effect/ReadonlyArray";
|
|
7
|
-
import { NanoId } from "./Crypto.js";
|
|
7
|
+
import { NanoId, NanoIdGenerator } from "./Crypto.js";
|
|
8
8
|
|
|
9
9
|
export interface OnCompletes {
|
|
10
10
|
readonly add: (onComplete: OnComplete) => Effect.Effect<OnCompleteId>;
|
|
@@ -16,7 +16,7 @@ export interface OnCompletes {
|
|
|
16
16
|
|
|
17
17
|
export type OnComplete = () => void;
|
|
18
18
|
|
|
19
|
-
export type OnCompleteId =
|
|
19
|
+
export type OnCompleteId = NanoId & Brand.Brand<"OnCompleteId">;
|
|
20
20
|
|
|
21
21
|
export const OnCompletes = Context.GenericTag<OnCompletes>(
|
|
22
22
|
"@services/OnCompletes",
|
|
@@ -25,12 +25,12 @@ export const OnCompletes = Context.GenericTag<OnCompletes>(
|
|
|
25
25
|
export const OnCompletesLive = Layer.effect(
|
|
26
26
|
OnCompletes,
|
|
27
27
|
Effect.gen(function* (_) {
|
|
28
|
-
const nanoid = yield* _(
|
|
28
|
+
const { nanoid } = yield* _(NanoIdGenerator);
|
|
29
29
|
const map = new Map<OnCompleteId, OnComplete>();
|
|
30
30
|
|
|
31
31
|
return OnCompletes.of({
|
|
32
32
|
add: (onComplete) =>
|
|
33
|
-
nanoid.
|
|
33
|
+
nanoid.pipe(
|
|
34
34
|
Effect.map((nanoid) => {
|
|
35
35
|
const id = nanoid as OnCompleteId;
|
|
36
36
|
map.set(id, onComplete);
|
package/src/Platform.ts
CHANGED
|
@@ -2,11 +2,7 @@ import * as Context from "effect/Context";
|
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
4
|
|
|
5
|
-
export type PlatformName =
|
|
6
|
-
| "server"
|
|
7
|
-
| "web-with-opfs"
|
|
8
|
-
| "web-without-opfs"
|
|
9
|
-
| "react-native";
|
|
5
|
+
export type PlatformName = "server" | "web" | "react-native";
|
|
10
6
|
|
|
11
7
|
export const PlatformName = Context.GenericTag<PlatformName>(
|
|
12
8
|
"@services/PlatformName",
|
|
@@ -40,6 +36,11 @@ export interface SyncLock {
|
|
|
40
36
|
|
|
41
37
|
export const SyncLock = Context.GenericTag<SyncLock>("@services/SyncLock");
|
|
42
38
|
|
|
39
|
+
export type DbWorkerLock = (callback: () => Promise<void>) => void;
|
|
40
|
+
export const DbWorkerLock = Context.GenericTag<DbWorkerLock>(
|
|
41
|
+
"@services/DbWorkerLock",
|
|
42
|
+
);
|
|
43
|
+
|
|
43
44
|
export type Fetch = (
|
|
44
45
|
url: string,
|
|
45
46
|
body: Uint8Array,
|
package/src/Sql.ts
CHANGED
|
@@ -3,33 +3,33 @@
|
|
|
3
3
|
// [Playground Link](https://kyse.link/?p=b&i=haFkpnNxbGl0ZaF2pjAuMjYuMKFz2gFoaW1wb3J0IHsgR2VuZXJhdGVkIH0gZnJvbSAna3lzZWx5JwoKZGVjbGFyZSBnbG9iYWwgewogIGludGVyZmFjZSBEQiB7CiAgICBldm9sdV9tZXNzYWdlOiB7CiAgICAgIHRpbWVzdGFtcDogc3RyaW5nLAogICAgICB0YWJsZTogc3RyaW5nLAogICAgICByb3c6IHN0cmluZywKICAgICAgY29sdW1uOiBzdHJpbmcsCiAgICAgIHZhbHVlOiB1bmtub3duCiAgICB9LAoKICAgIGV2b2x1X293bmVyOiB7CiAgICAgIGlkOiBzdHJpbmcKICAgICAgbW5lbW9uaWM6IHN0cmluZwogICAgICBlbmNyeXB0aW9uS2V5OiBVaW50OEFycmF5LAogICAgICB0aW1lc3RhbXA6IHN0cmluZywKICAgICAgbWVya2xlVHJlZTogc3RyaW5nCiAgICB9CiAgfQp9oXHaBnhhd2FpdCBreXNlbHkuc2VsZWN0RnJvbSgiZXZvbHVfb3duZXIiKQogIC5zZWxlY3QoWyJpZCIsICJtbmVtb25pYyIsICJlbmNyeXB0aW9uS2V5Il0pCiAgLmV4ZWN1dGUoKQoKYXdhaXQga3lzZWx5LnNjaGVtYQogIC5jcmVhdGVUYWJsZSgnZXZvbHVfbWVzc2FnZScpCiAgLmFkZENvbHVtbigndGltZXN0YW1wJywgJ2Jsb2InLCBjb2wgPT4gY29sLnByaW1hcnlLZXkoKSkKICAuYWRkQ29sdW1uKCd0YWJsZScsICdibG9iJykKICAuYWRkQ29sdW1uKCdyb3cnLCAnYmxvYicpCiAgLmFkZENvbHVtbignY29sdW1uJywgJ2Jsb2InKQogIC5hZGRDb2x1bW4oJ3ZhbHVlJywgJ2Jsb2InKQogIC5leGVjdXRlKCkKCmF3YWl0IGt5c2VseS5zY2hlbWEKICAuY3JlYXRlSW5kZXgoImluZGV4X2V2b2x1X21lc3NhZ2UiKQogIC5vbigiZXZvbHVfbWVzc2FnZSIpCiAgLmNvbHVtbnMoWyJ0YWJsZSIsICJyb3ciLCAiY29sdW1uIiwgInRpbWVzdGFtcCJdKQogIC5leGVjdXRlKCkKCmF3YWl0IGt5c2VseS5zY2hlbWEKICAuY3JlYXRlVGFibGUoJ2V2b2x1X19vd25lcicpCiAgLmFkZENvbHVtbignaWQnLCAnYmxvYicpCiAgLmFkZENvbHVtbignbW5lbW9uaWMnLCAnYmxvYicpCiAgLmFkZENvbHVtbignZW5jcnlwdGlvbktleScsICdibG9iJykKICAuYWRkQ29sdW1uKCd0aW1lc3RhbXAnLCAnYmxvYicpCiAgLmFkZENvbHVtbignbWVya2xlVHJlZScsICdibG9iJykKICAuZXhlY3V0ZSgpCgphd2FpdCBreXNlbHkuaW5zZXJ0SW50bygiZXZvbHVfb3duZXIiKQogIC52YWx1ZXMoewogICAgImlkIjogImIiLAogICAgIm1uZW1vbmljIjogImEiLAogICAgImVuY3J5cHRpb25LZXkiOiBuZXcgVWludDhBcnJheSgpLAogICAgInRpbWVzdGFtcCI6ICJhIiwKICAgICJtZXJrbGVUcmVlIjogImIiCiAgfSkKICAuZXhlY3V0ZSgpCgphd2FpdCBreXNlbHkuc2VsZWN0RnJvbSgiZXZvbHVfb3duZXIiKQogIC5zZWxlY3QoWyJ0aW1lc3RhbXAiLCAibWVya2xlVHJlZSJdKQogIC5leGVjdXRlKCkKCmF3YWl0IGt5c2VseS5zZWxlY3RGcm9tKCJldm9sdV9tZXNzYWdlIikKICAuc2VsZWN0KCJ0aW1lc3RhbXAiKQogIC53aGVyZSgndGFibGUnLCAnPScsICcxJykKICAud2hlcmUoJ3JvdycsICc9JywgJzInKQogIC53aGVyZSgnY29sdW1uJywgJz0nLCAnMycpCiAgLm9yZGVyQnkoInRpbWVzdGFtcCIsICJkZXNjIikKICAubGltaXQoMSkKICAuZXhlY3V0ZVRha2VGaXJzdCgpCgphd2FpdCBreXNlbHkuaW5zZXJ0SW50bygiZXZvbHVfbWVzc2FnZSIpCiAgLnZhbHVlcyh7CiAgICAidGltZXN0YW1wIjogJzEnLAogICAgInRhYmxlIjogJzInLAogICAgInJvdyI6ICczJywKICAgICJjb2x1bW4iOiAnNCcsCiAgICAidmFsdWUiOiAnNScsCiAgfSkKICAub25Db25mbGljdChvYyA9PiBvYy5kb05vdGhpbmcoKSkKICAuZXhlY3V0ZSgpCgphd2FpdCBreXNlbHkudXBkYXRlVGFibGUoImV2b2x1X293bmVyIikKICAuc2V0KHsKICAgICJtZXJrbGVUcmVlIjogJzEnLAogICAgInRpbWVzdGFtcCI6ICIyIgogIH0pCiAgLmV4ZWN1dGUoKQoKYXdhaXQga3lzZWx5LnNlbGVjdEZyb20oImV2b2x1X21lc3NhZ2UiKQogIC5zZWxlY3RBbGwoKQogIC53aGVyZSgidGltZXN0YW1wIiwgIj49IiwgJzEnKQogIC5vcmRlckJ5KCJ0aW1lc3RhbXAiKQogIC5leGVjdXRlKCmhY8M=)
|
|
4
4
|
|
|
5
5
|
export const selectOwner = `
|
|
6
|
-
|
|
6
|
+
select
|
|
7
7
|
"id",
|
|
8
8
|
"mnemonic",
|
|
9
9
|
"encryptionKey"
|
|
10
|
-
|
|
10
|
+
from
|
|
11
11
|
"evolu_owner"
|
|
12
|
-
|
|
12
|
+
`.trim();
|
|
13
13
|
|
|
14
14
|
export const createMessageTable = `
|
|
15
|
-
|
|
15
|
+
create table
|
|
16
16
|
"evolu_message" (
|
|
17
|
-
"timestamp" blob
|
|
17
|
+
"timestamp" blob primary key,
|
|
18
18
|
"table" blob,
|
|
19
19
|
"row" blob,
|
|
20
20
|
"column" blob,
|
|
21
21
|
"value" blob
|
|
22
22
|
);
|
|
23
|
-
|
|
23
|
+
`.trim();
|
|
24
24
|
|
|
25
25
|
export const createMessageTableIndex = `
|
|
26
|
-
|
|
26
|
+
create index "index_evolu_message" on "evolu_message" (
|
|
27
27
|
"table", "row", "column", "timestamp"
|
|
28
28
|
);
|
|
29
|
-
|
|
29
|
+
`.trim();
|
|
30
30
|
|
|
31
31
|
export const createOwnerTable = `
|
|
32
|
-
|
|
32
|
+
create table
|
|
33
33
|
"evolu_owner" (
|
|
34
34
|
"id" blob,
|
|
35
35
|
"mnemonic" blob,
|
|
@@ -37,10 +37,10 @@ CREATE TABLE
|
|
|
37
37
|
"timestamp" blob,
|
|
38
38
|
"merkleTree" blob
|
|
39
39
|
);
|
|
40
|
-
|
|
40
|
+
`.trim();
|
|
41
41
|
|
|
42
42
|
export const insertOwner = `
|
|
43
|
-
|
|
43
|
+
insert into
|
|
44
44
|
"evolu_owner" (
|
|
45
45
|
"id",
|
|
46
46
|
"mnemonic",
|
|
@@ -48,73 +48,76 @@ INSERT INTO
|
|
|
48
48
|
"timestamp",
|
|
49
49
|
"merkleTree"
|
|
50
50
|
)
|
|
51
|
-
|
|
51
|
+
values
|
|
52
52
|
(?, ?, ?, ?, ?);
|
|
53
|
-
|
|
53
|
+
`.trim();
|
|
54
54
|
|
|
55
55
|
export const selectOwnerTimestampAndMerkleTree = `
|
|
56
|
-
|
|
56
|
+
select
|
|
57
57
|
"timestamp",
|
|
58
58
|
"merkleTree"
|
|
59
|
-
|
|
59
|
+
from
|
|
60
60
|
"evolu_owner"
|
|
61
|
-
|
|
61
|
+
`.trim();
|
|
62
62
|
|
|
63
63
|
export const selectLastTimestampForTableRowColumn = `
|
|
64
|
-
|
|
64
|
+
select
|
|
65
65
|
"timestamp"
|
|
66
|
-
|
|
66
|
+
from
|
|
67
67
|
"evolu_message"
|
|
68
|
-
|
|
68
|
+
where
|
|
69
69
|
"table" = ?
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"timestamp"
|
|
74
|
-
|
|
70
|
+
and "row" = ?
|
|
71
|
+
and "column" = ?
|
|
72
|
+
order by
|
|
73
|
+
"timestamp" desc
|
|
74
|
+
limit
|
|
75
75
|
1
|
|
76
|
-
|
|
76
|
+
`.trim();
|
|
77
77
|
|
|
78
78
|
export const upsertValueIntoTableRowColumn = (
|
|
79
79
|
table: string,
|
|
80
80
|
column: string,
|
|
81
|
-
): string =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
): string =>
|
|
82
|
+
`
|
|
83
|
+
insert into
|
|
84
|
+
"${table}" ("id", "${column}", "createdAt", "updatedAt")
|
|
85
|
+
values
|
|
86
|
+
(?, ?, ?, ?)
|
|
87
|
+
on conflict do update set
|
|
88
|
+
"${column}" = ?,
|
|
89
|
+
"updatedAt" = ?
|
|
90
|
+
`.trim();
|
|
89
91
|
|
|
90
|
-
export const deleteTableRow = (table: string): string =>
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
export const deleteTableRow = (table: string): string =>
|
|
93
|
+
`
|
|
94
|
+
delete from "${table}"
|
|
95
|
+
where
|
|
93
96
|
"id" = ?;
|
|
94
|
-
|
|
97
|
+
`.trim();
|
|
95
98
|
|
|
96
99
|
export const insertIntoMessagesIfNew = `
|
|
97
|
-
|
|
100
|
+
insert into
|
|
98
101
|
"evolu_message" ("timestamp", "table", "row", "column", "value")
|
|
99
|
-
|
|
102
|
+
values
|
|
100
103
|
(?, ?, ?, ?, ?)
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
on conflict do nothing
|
|
105
|
+
`.trim();
|
|
103
106
|
|
|
104
107
|
export const updateOwnerTimestampAndMerkleTree = `
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
update "evolu_owner"
|
|
109
|
+
set
|
|
107
110
|
"timestamp" = ?,
|
|
108
111
|
"merkleTree" = ?
|
|
109
|
-
|
|
112
|
+
`.trim();
|
|
110
113
|
|
|
111
114
|
export const selectMessagesToSync = `
|
|
112
|
-
|
|
115
|
+
select
|
|
113
116
|
*
|
|
114
|
-
|
|
117
|
+
from
|
|
115
118
|
"evolu_message"
|
|
116
|
-
|
|
119
|
+
where
|
|
117
120
|
"timestamp" >= ?
|
|
118
|
-
|
|
121
|
+
order by
|
|
119
122
|
"timestamp"
|
|
120
|
-
|
|
123
|
+
`.trim();
|
package/src/Sqlite.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as Effect from "effect/Effect";
|
|
|
3
3
|
import * as Predicate from "effect/Predicate";
|
|
4
4
|
|
|
5
5
|
export interface Sqlite {
|
|
6
|
-
readonly exec: (arg: string | SqliteQuery) => Effect.Effect<
|
|
6
|
+
readonly exec: (arg: string | SqliteQuery) => Effect.Effect<SqliteExecResult>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const Sqlite = Context.GenericTag<Sqlite>("@services/Sqlite");
|
|
@@ -21,7 +21,7 @@ type JsonArray = ReadonlyArray<Json>;
|
|
|
21
21
|
type JsonPrimitive = string | number | boolean | null;
|
|
22
22
|
type Json = JsonPrimitive | JsonObject | JsonArray;
|
|
23
23
|
|
|
24
|
-
interface
|
|
24
|
+
export interface SqliteExecResult {
|
|
25
25
|
readonly rows: SqliteRow[];
|
|
26
26
|
readonly changes: number;
|
|
27
27
|
}
|
|
@@ -80,3 +80,19 @@ const parseObject = (o: Record<string, unknown>): Record<string, unknown> => {
|
|
|
80
80
|
for (const key in o) o[key] = parse(o[key]);
|
|
81
81
|
return o;
|
|
82
82
|
};
|
|
83
|
+
|
|
84
|
+
/** This is good enough detection because SQL strings in Evolu are predictable. */
|
|
85
|
+
const isSqlMutationRegEx = new RegExp(
|
|
86
|
+
`\\b(${[
|
|
87
|
+
"alter",
|
|
88
|
+
"create",
|
|
89
|
+
"delete",
|
|
90
|
+
"drop",
|
|
91
|
+
"insert",
|
|
92
|
+
"replace",
|
|
93
|
+
"update",
|
|
94
|
+
].join("|")})\\b`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
export const isSqlMutation = (sql: string): boolean =>
|
|
98
|
+
isSqlMutationRegEx.test(sql);
|
package/src/SyncWorker.ts
CHANGED
|
@@ -31,12 +31,10 @@ import {
|
|
|
31
31
|
SyncResponse,
|
|
32
32
|
} from "./Protobuf.js";
|
|
33
33
|
import { JsonObjectOrArray, Value } from "./Sqlite.js";
|
|
34
|
+
import { Messaging } from "./Types.js";
|
|
34
35
|
|
|
35
|
-
export interface SyncWorker
|
|
36
|
-
|
|
37
|
-
onMessage: (output: SyncWorkerOutput) => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
36
|
+
export interface SyncWorker
|
|
37
|
+
extends Messaging<SyncWorkerInput, SyncWorkerOutput> {}
|
|
40
38
|
export const SyncWorker = Context.GenericTag<SyncWorker>(
|
|
41
39
|
"@services/SyncWorker",
|
|
42
40
|
);
|
|
@@ -325,7 +323,7 @@ const sync = (
|
|
|
325
323
|
);
|
|
326
324
|
});
|
|
327
325
|
|
|
328
|
-
export const
|
|
326
|
+
export const SyncWorkerCommonLive = Layer.effect(
|
|
329
327
|
SyncWorker,
|
|
330
328
|
Effect.gen(function* (_) {
|
|
331
329
|
const syncLock = yield* _(SyncLock);
|