@evolu/common 1.0.13 → 1.0.15
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/Db.d.ts +44 -3
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +19 -13
- package/dist/src/DbWorker.d.ts +3 -2
- package/dist/src/DbWorker.d.ts.map +1 -1
- package/dist/src/DbWorker.js +47 -39
- package/dist/src/Evolu.d.ts +3 -3
- package/dist/src/Model.d.ts +0 -25
- package/dist/src/Model.d.ts.map +1 -1
- package/dist/src/Sql.d.ts +2 -1
- package/dist/src/Sql.d.ts.map +1 -1
- package/dist/src/Sql.js +6 -1
- package/dist/src/index.d.ts +0 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +0 -3
- package/package.json +6 -5
- package/src/Db.ts +87 -23
- package/src/DbWorker.ts +90 -69
- package/src/Evolu.ts +22 -22
- package/src/Model.ts +1 -30
- package/src/Sql.ts +7 -1
- package/src/index.ts +0 -3
package/dist/src/Db.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as S from "@effect/schema/Schema";
|
|
2
2
|
import { Brand, Context, Effect, ReadonlyRecord } from "effect";
|
|
3
3
|
import * as Kysely from "kysely";
|
|
4
|
+
import { Simplify } from "kysely";
|
|
4
5
|
import { Bip39, Mnemonic, NanoId } from "./Crypto.js";
|
|
5
6
|
import { Id, SqliteBoolean, SqliteDate } from "./Model.js";
|
|
6
7
|
import { Query, Row, Sqlite, Value } from "./Sqlite.js";
|
|
@@ -9,9 +10,9 @@ export type TableSchema = ReadonlyRecord.ReadonlyRecord<Value> & {
|
|
|
9
10
|
};
|
|
10
11
|
export type Schema = ReadonlyRecord.ReadonlyRecord<TableSchema>;
|
|
11
12
|
export type CreateQuery<S extends Schema> = (queryCallback: QueryCallback<S, Row>) => Query;
|
|
12
|
-
export type QueryCallback<S extends Schema, QueryRow> = (db: KyselyWithoutMutation<
|
|
13
|
+
export type QueryCallback<S extends Schema, QueryRow> = (db: KyselyWithoutMutation<QuerySchema<S>>) => Kysely.SelectQueryBuilder<any, any, QueryRow>;
|
|
13
14
|
type KyselyWithoutMutation<DB> = Pick<Kysely.Kysely<DB>, "selectFrom" | "fn">;
|
|
14
|
-
type
|
|
15
|
+
type QuerySchema<S extends Schema> = {
|
|
15
16
|
readonly [Table in keyof S]: NullableExceptOfId<{
|
|
16
17
|
readonly [Column in keyof S[Table]]: S[Table][Column];
|
|
17
18
|
} & CommonColumns>;
|
|
@@ -24,12 +25,45 @@ export interface CommonColumns {
|
|
|
24
25
|
readonly updatedAt: SqliteDate;
|
|
25
26
|
readonly isDeleted: SqliteBoolean;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Filter and map array items in one step with the correct return type and
|
|
30
|
+
* without unreliable TypeScript type guards.
|
|
31
|
+
*
|
|
32
|
+
* ### Examples
|
|
33
|
+
*
|
|
34
|
+
* ```
|
|
35
|
+
* useQuery(
|
|
36
|
+
* (db) => db.selectFrom("todo").selectAll(),
|
|
37
|
+
* // Filter and map nothing.
|
|
38
|
+
* (row) => row,
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* useQuery(
|
|
42
|
+
* (db) => db.selectFrom("todo").selectAll(),
|
|
43
|
+
* // Filter items with title != null.
|
|
44
|
+
* // Note the title type isn't nullable anymore in rows.
|
|
45
|
+
* ({ title, ...rest }) => title != null && { title, ...rest },
|
|
46
|
+
* );
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export type FilterMap<QueryRow extends Row, FilterMapRow extends Row> = (row: QueryRow) => FilterMapRow | null | false;
|
|
50
|
+
export interface QueryResult<FilterMapRow extends Row> {
|
|
51
|
+
/**
|
|
52
|
+
* Rows from the database. They can be filtered and mapped by `filterMap`.
|
|
53
|
+
*/
|
|
54
|
+
readonly rows: ReadonlyArray<Readonly<Simplify<ExcludeNullAndFalse<FilterMapRow>>>>;
|
|
55
|
+
/**
|
|
56
|
+
* The first row from `rows`. For empty rows, it's null.
|
|
57
|
+
*/
|
|
58
|
+
readonly firstRow: Readonly<Simplify<ExcludeNullAndFalse<FilterMapRow>>> | null;
|
|
59
|
+
}
|
|
60
|
+
type ExcludeNullAndFalse<T> = Exclude<T, null | false>;
|
|
27
61
|
export interface Table {
|
|
28
62
|
readonly name: string;
|
|
29
63
|
readonly columns: ReadonlyArray<string>;
|
|
30
64
|
}
|
|
31
65
|
export type Tables = ReadonlyArray<Table>;
|
|
32
|
-
export declare const makeCreateQuery: <
|
|
66
|
+
export declare const makeCreateQuery: <S extends Schema>() => CreateQuery<S>;
|
|
33
67
|
export declare const schemaToTables: (schema: S.Schema<any, any>) => Tables;
|
|
34
68
|
/**
|
|
35
69
|
* `Owner` represents the Evolu database owner. Evolu auto-generates `Owner`
|
|
@@ -52,9 +86,16 @@ export declare const transaction: <R, E, A>(effect: Effect.Effect<R, E, A>) => E
|
|
|
52
86
|
export interface NoSuchTableOrColumnError {
|
|
53
87
|
readonly _tag: "NoSuchTableOrColumnError";
|
|
54
88
|
}
|
|
89
|
+
export declare const SqliteNoSuchTableOrColumnError: S.Schema<{
|
|
90
|
+
readonly message: string;
|
|
91
|
+
}, {
|
|
92
|
+
readonly message: string;
|
|
93
|
+
}>;
|
|
94
|
+
export type SqliteNoSuchTableOrColumnError = S.Schema.To<typeof SqliteNoSuchTableOrColumnError>;
|
|
55
95
|
export declare const someDefectToNoSuchTableOrColumnError: <R, E, A>(self: Effect.Effect<R, E, A>) => Effect.Effect<R, NoSuchTableOrColumnError | E, A>;
|
|
56
96
|
export declare const makeOwner: (mnemonic?: Mnemonic) => Effect.Effect<Bip39, never, Owner>;
|
|
57
97
|
export declare const lazyInit: (mnemonic?: Mnemonic) => Effect.Effect<Sqlite | Bip39 | NanoId, never, Owner>;
|
|
58
98
|
export declare const ensureSchema: (tables: Tables) => Effect.Effect<Sqlite, never, void>;
|
|
99
|
+
export declare const makeCacheFilterMap: () => <QueryRow extends Row, FilterMapRow extends Row>(filterMap: FilterMap<QueryRow, FilterMapRow>) => FilterMap<QueryRow, FilterMapRow>;
|
|
59
100
|
export {};
|
|
60
101
|
//# sourceMappingURL=Db.d.ts.map
|
package/dist/src/Db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,uBAAuB,CAAC;AAG3C,OAAO,EACL,KAAK,EACL,OAAO,EACP,MAAM,EAKN,cAAc,EAGf,MAAM,QAAQ,CAAC;AAChB,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../src/Db.ts"],"names":[],"mappings":"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"}
|
package/dist/src/Db.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as AST from "@effect/schema/AST";
|
|
2
|
+
import * as S from "@effect/schema/Schema";
|
|
2
3
|
import { make } from "@effect/schema/Schema";
|
|
3
4
|
import { bytesToHex } from "@noble/ciphers/utils";
|
|
4
5
|
import { Context, Effect, Exit, Option, Predicate, ReadonlyArray, ReadonlyRecord, String, pipe, } from "effect";
|
|
@@ -41,20 +42,14 @@ schema) => pipe(getPropertySignatures(schema), ReadonlyRecord.toEntries, Readonl
|
|
|
41
42
|
})));
|
|
42
43
|
export const Owner = Context.Tag("evolu/Owner");
|
|
43
44
|
export const transaction = (effect) => Effect.flatMap(Sqlite, (sqlite) => Effect.acquireUseRelease(sqlite.exec("BEGIN"), () => effect, (_, exit) => Exit.isFailure(exit) ? sqlite.exec("ROLLBACK") : sqlite.exec("END")));
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
error != null &&
|
|
47
|
-
"message" in error &&
|
|
48
|
-
typeof error.message === "string" &&
|
|
49
|
-
error.message.includes("code 1") &&
|
|
50
|
-
(error.message.includes("no such table") ||
|
|
51
|
-
error.message.includes("no such column") ||
|
|
52
|
-
error.message.includes("has no column")))
|
|
53
|
-
return Option.some(Effect.fail({
|
|
54
|
-
_tag: "NoSuchTableOrColumnError",
|
|
55
|
-
}));
|
|
56
|
-
return Option.none();
|
|
45
|
+
export const SqliteNoSuchTableOrColumnError = S.struct({
|
|
46
|
+
message: S.union(S.string.pipe(S.includes("no such table")), S.string.pipe(S.includes("no such column")), S.string.pipe(S.includes("has no column"))),
|
|
57
47
|
});
|
|
48
|
+
export const someDefectToNoSuchTableOrColumnError = Effect.catchSomeDefect((error) => S.is(SqliteNoSuchTableOrColumnError)(error)
|
|
49
|
+
? Option.some(Effect.fail({
|
|
50
|
+
_tag: "NoSuchTableOrColumnError",
|
|
51
|
+
}))
|
|
52
|
+
: Option.none());
|
|
58
53
|
export const makeOwner = (mnemonic) => Effect.gen(function* (_) {
|
|
59
54
|
const bip39 = yield* _(Bip39);
|
|
60
55
|
if (mnemonic == null)
|
|
@@ -115,3 +110,14 @@ const createTable = ({ name, columns, }) => Effect.flatMap(Sqlite, (sqlite) => s
|
|
|
115
110
|
export const ensureSchema = (tables) => Effect.flatMap(getTables, (existingTables) => Effect.forEach(tables, (tableDefinition) => existingTables.includes(tableDefinition.name)
|
|
116
111
|
? updateTable(tableDefinition)
|
|
117
112
|
: createTable(tableDefinition), { discard: true }));
|
|
113
|
+
export const makeCacheFilterMap = () => {
|
|
114
|
+
const cache = new WeakMap();
|
|
115
|
+
return (filterMap) => (row) => {
|
|
116
|
+
let cachedRow = cache.get(row);
|
|
117
|
+
if (cachedRow === undefined) {
|
|
118
|
+
cachedRow = filterMap(row);
|
|
119
|
+
cache.set(row, cachedRow);
|
|
120
|
+
}
|
|
121
|
+
return cachedRow;
|
|
122
|
+
};
|
|
123
|
+
};
|
package/dist/src/DbWorker.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Brand, Context, Layer, ReadonlyArray, ReadonlyRecord } from "effect";
|
|
1
|
+
import { Brand, Context, Effect, Layer, ReadonlyArray, ReadonlyRecord } from "effect";
|
|
2
2
|
import { Config } from "./Config.js";
|
|
3
3
|
import { Bip39, Mnemonic, NanoId } from "./Crypto.js";
|
|
4
4
|
import { Owner, Tables } from "./Db.js";
|
|
@@ -73,7 +73,8 @@ export interface MutateItem {
|
|
|
73
73
|
readonly onCompleteId: OnCompleteId | null;
|
|
74
74
|
}
|
|
75
75
|
export type RowsCacheMap = ReadonlyMap<Query, ReadonlyArray<Row>>;
|
|
76
|
-
export declare const mutateItemsToNewMessages: (items:
|
|
76
|
+
export declare const mutateItemsToNewMessages: (items: ReadonlyArray<MutateItem>) => ReadonlyArray<NewMessage>;
|
|
77
|
+
export declare const upsertValueIntoTableRowColumn: (message: NewMessage, messages: ReadonlyArray<NewMessage>) => Effect.Effect<Sqlite, never, void>;
|
|
77
78
|
export declare const DbWorkerLive: Layer.Layer<Bip39 | NanoId | Sqlite | SyncWorker, never, DbWorker>;
|
|
78
79
|
export {};
|
|
79
80
|
//# sourceMappingURL=DbWorker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DbWorker.d.ts","sourceRoot":"","sources":["../../src/DbWorker.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EACL,OAAO,
|
|
1
|
+
{"version":3,"file":"DbWorker.d.ts","sourceRoot":"","sources":["../../src/DbWorker.ts"],"names":[],"mappings":"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"}
|
package/dist/src/DbWorker.js
CHANGED
|
@@ -7,14 +7,14 @@ import { Owner, ensureSchema, lazyInit, someDefectToNoSuchTableOrColumnError, tr
|
|
|
7
7
|
import { makePatches } from "./Diff.js";
|
|
8
8
|
import { makeUnexpectedError } from "./Errors.js";
|
|
9
9
|
import { cast } from "./Model.js";
|
|
10
|
-
import
|
|
10
|
+
import * as Sql from "./Sql.js";
|
|
11
11
|
import { Sqlite, queryObjectFromQuery } from "./Sqlite.js";
|
|
12
12
|
import { NewMessageEquivalence, SyncWorker, SyncWorkerPostMessage, } from "./SyncWorker.js";
|
|
13
13
|
export const DbWorker = Context.Tag("evolu/DbWorker");
|
|
14
14
|
const DbWorkerOnMessage = Context.Tag("evolu/DbWorkerOnMessage");
|
|
15
15
|
const init = (input) => Effect.gen(function* (_) {
|
|
16
16
|
const sqlite = yield* _(Sqlite);
|
|
17
|
-
return yield* _(sqlite.exec(selectOwner), Effect.map(({ rows: [row] }) => ({
|
|
17
|
+
return yield* _(sqlite.exec(Sql.selectOwner), Effect.map(({ rows: [row] }) => ({
|
|
18
18
|
id: row.id,
|
|
19
19
|
mnemonic: row.mnemonic,
|
|
20
20
|
// expo-sqlite 11.3.2 doesn't support Uint8Array
|
|
@@ -37,11 +37,11 @@ const query = ({ queries, onCompleteIds = [], }) => Effect.gen(function* (_) {
|
|
|
37
37
|
}));
|
|
38
38
|
dbWorkerOnMessage({ _tag: "onQuery", queriesPatches, onCompleteIds });
|
|
39
39
|
});
|
|
40
|
-
const readTimestampAndMerkleTree = Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec(selectOwnerTimestampAndMerkleTree)), Effect.map((result) => result.rows), Effect.map(([{ timestamp, merkleTree }]) => ({
|
|
40
|
+
const readTimestampAndMerkleTree = Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree)), Effect.map((result) => result.rows), Effect.map(([{ timestamp, merkleTree }]) => ({
|
|
41
41
|
timestamp: unsafeTimestampFromString(timestamp),
|
|
42
42
|
merkleTree: merkleTree,
|
|
43
43
|
})));
|
|
44
|
-
export const mutateItemsToNewMessages = (items) => pipe(items, ReadonlyArray.
|
|
44
|
+
export const mutateItemsToNewMessages = (items) => pipe(items, ReadonlyArray.map(({ id, isInsert, now, table, values }) => pipe(Object.entries(values), ReadonlyArray.filterMap(([key, value]) =>
|
|
45
45
|
// The value can be undefined if exactOptionalPropertyTypes isn't true.
|
|
46
46
|
// Don't insert nulls because null is the default value.
|
|
47
47
|
value === undefined || (isInsert && value == null)
|
|
@@ -53,13 +53,13 @@ value === undefined || (isInsert && value == null)
|
|
|
53
53
|
: value instanceof Date
|
|
54
54
|
? cast(value)
|
|
55
55
|
: value,
|
|
56
|
-
]), ReadonlyArray.append([isInsert ? "createdAt" : "updatedAt", now]), ReadonlyArray.
|
|
56
|
+
]), ReadonlyArray.append([isInsert ? "createdAt" : "updatedAt", now]), ReadonlyArray.map(([key, value]) => ({
|
|
57
57
|
table,
|
|
58
58
|
row: id,
|
|
59
59
|
column: key,
|
|
60
60
|
value,
|
|
61
|
-
})))), ReadonlyArray.
|
|
62
|
-
const
|
|
61
|
+
})))), ReadonlyArray.flatten, ReadonlyArray.dedupeWith(NewMessageEquivalence));
|
|
62
|
+
const ensureSchemaByNewMessages = (messages) => Effect.gen(function* (_) {
|
|
63
63
|
const tablesMap = new Map();
|
|
64
64
|
messages.forEach((message) => {
|
|
65
65
|
const table = tablesMap.get(message.table);
|
|
@@ -79,23 +79,29 @@ const ensureSchemaByMessages = (messages) => Effect.gen(function* (_) {
|
|
|
79
79
|
});
|
|
80
80
|
yield* _(ensureSchema(Array.from(tablesMap.values())));
|
|
81
81
|
});
|
|
82
|
+
export const upsertValueIntoTableRowColumn = (message, messages) => Effect.gen(function* (_) {
|
|
83
|
+
const sqlite = yield* _(Sqlite);
|
|
84
|
+
const insert = sqlite.exec({
|
|
85
|
+
sql: Sql.upsertValueIntoTableRowColumn(message.table, message.column),
|
|
86
|
+
parameters: [message.row, message.value, message.value],
|
|
87
|
+
});
|
|
88
|
+
yield* _(insert, someDefectToNoSuchTableOrColumnError, Effect.catchTag("NoSuchTableOrColumnError", () =>
|
|
89
|
+
// If one message fails, we ensure schema for all messages.
|
|
90
|
+
ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert))));
|
|
91
|
+
});
|
|
82
92
|
const applyMessages = ({ merkleTree, messages, }) => Effect.gen(function* (_) {
|
|
83
93
|
const sqlite = yield* _(Sqlite);
|
|
84
94
|
for (const message of messages) {
|
|
85
95
|
const timestamp = yield* _(sqlite.exec({
|
|
86
|
-
sql: selectLastTimestampForTableRowColumn,
|
|
96
|
+
sql: Sql.selectLastTimestampForTableRowColumn,
|
|
87
97
|
parameters: [message.table, message.row, message.column],
|
|
88
98
|
}), Effect.map((result) => result.rows), Effect.flatMap(ReadonlyArray.head), Effect.map((row) => row.timestamp), Effect.catchTag("NoSuchElementException", () => Effect.succeed(null)));
|
|
89
99
|
if (timestamp == null || timestamp < message.timestamp) {
|
|
90
|
-
|
|
91
|
-
sql: insertValueIntoTableRowColumn(message.table, message.column),
|
|
92
|
-
parameters: [message.row, message.value, message.value],
|
|
93
|
-
});
|
|
94
|
-
yield* _(insert, someDefectToNoSuchTableOrColumnError, Effect.catchTag("NoSuchTableOrColumnError", () => ensureSchemaByMessages(messages).pipe(Effect.flatMap(() => insert))));
|
|
100
|
+
yield* _(upsertValueIntoTableRowColumn(message, messages));
|
|
95
101
|
}
|
|
96
102
|
if (timestamp == null || timestamp !== message.timestamp) {
|
|
97
103
|
const { changes } = yield* _(sqlite.exec({
|
|
98
|
-
sql: insertIntoMessagesIfNew,
|
|
104
|
+
sql: Sql.insertIntoMessagesIfNew,
|
|
99
105
|
parameters: [
|
|
100
106
|
message.timestamp,
|
|
101
107
|
message.table,
|
|
@@ -104,50 +110,52 @@ const applyMessages = ({ merkleTree, messages, }) => Effect.gen(function* (_) {
|
|
|
104
110
|
message.value,
|
|
105
111
|
],
|
|
106
112
|
}));
|
|
107
|
-
if (changes > 0)
|
|
108
|
-
|
|
113
|
+
if (changes > 0) {
|
|
114
|
+
const timestamp = unsafeTimestampFromString(message.timestamp);
|
|
115
|
+
merkleTree = insertIntoMerkleTree(timestamp)(merkleTree);
|
|
116
|
+
}
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
return merkleTree;
|
|
112
120
|
});
|
|
113
121
|
const writeTimestampAndMerkleTree = ({ timestamp, merkleTree, }) => Effect.flatMap(Sqlite, (sqlite) => sqlite.exec({
|
|
114
|
-
sql: updateOwnerTimestampAndMerkleTree,
|
|
122
|
+
sql: Sql.updateOwnerTimestampAndMerkleTree,
|
|
115
123
|
parameters: [
|
|
116
124
|
timestampToString(timestamp),
|
|
117
125
|
merkleTreeToString(merkleTree),
|
|
118
126
|
],
|
|
119
127
|
}));
|
|
120
128
|
const mutate = ({ items, queries, }) => Effect.gen(function* (_) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const [toSync, localOnlyItems] = ReadonlyArray.partition(items, (item) => item.table.startsWith("_"));
|
|
130
|
+
const [toUpsert, toDelete] = ReadonlyArray.partition(localOnlyItems, (item) => mutateItemsToNewMessages([item]).some((message) => message.column === "isDeleted" && message.value === 1)).map(mutateItemsToNewMessages);
|
|
131
|
+
yield* _(Effect.forEach(toUpsert, (message) => upsertValueIntoTableRowColumn(message, toUpsert)));
|
|
132
|
+
const { exec } = yield* _(Sqlite);
|
|
133
|
+
yield* _(Effect.forEach(toDelete, ({ table, row }) => exec({ sql: Sql.deleteTableRow(table), parameters: [row] })));
|
|
134
|
+
if (toSync.length > 0) {
|
|
135
|
+
let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
|
|
136
|
+
const messages = yield* _(mutateItemsToNewMessages(toSync), Effect.forEach((message) => Effect.map(sendTimestamp(timestamp), (nextTimestamp) => {
|
|
137
|
+
timestamp = nextTimestamp;
|
|
138
|
+
return { ...message, timestamp: timestampToString(timestamp) };
|
|
139
|
+
})));
|
|
131
140
|
merkleTree = yield* _(applyMessages({ merkleTree, messages }));
|
|
132
141
|
yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
|
|
142
|
+
(yield* _(SyncWorkerPostMessage))({
|
|
143
|
+
_tag: "sync",
|
|
144
|
+
syncUrl: (yield* _(Config)).syncUrl,
|
|
145
|
+
messages,
|
|
146
|
+
timestamp,
|
|
147
|
+
merkleTree,
|
|
148
|
+
owner: yield* _(Owner),
|
|
149
|
+
syncLoopCount: 0,
|
|
150
|
+
});
|
|
133
151
|
}
|
|
134
152
|
const onCompleteIds = ReadonlyArray.filterMap(items, (item) => Option.fromNullable(item.onCompleteId));
|
|
135
153
|
if (queries.length > 0 || onCompleteIds.length > 0)
|
|
136
154
|
yield* _(query({ queries, onCompleteIds }));
|
|
137
|
-
const [config, syncWorkerPostMessage] = yield* _(Effect.all([Config, SyncWorkerPostMessage]));
|
|
138
|
-
syncWorkerPostMessage({
|
|
139
|
-
_tag: "sync",
|
|
140
|
-
syncUrl: config.syncUrl,
|
|
141
|
-
messages,
|
|
142
|
-
timestamp,
|
|
143
|
-
merkleTree,
|
|
144
|
-
owner,
|
|
145
|
-
syncLoopCount: 0,
|
|
146
|
-
});
|
|
147
155
|
});
|
|
148
156
|
const handleSyncResponse = ({ messages, ...response }) => Effect.gen(function* (_) {
|
|
149
157
|
let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
|
|
150
|
-
if (
|
|
158
|
+
if (messages.length > 0) {
|
|
151
159
|
for (const message of messages)
|
|
152
160
|
timestamp = yield* _(unsafeTimestampFromString(message.timestamp), (remote) => receiveTimestamp({ local: timestamp, remote }));
|
|
153
161
|
merkleTree = yield* _(applyMessages({ merkleTree, messages }));
|
|
@@ -170,7 +178,7 @@ const handleSyncResponse = ({ messages, ...response }) => Effect.gen(function* (
|
|
|
170
178
|
}
|
|
171
179
|
const [sqlite, config, owner] = yield* _(Effect.all([Sqlite, Config, Owner]));
|
|
172
180
|
const messagesToSync = yield* _(sqlite.exec({
|
|
173
|
-
sql: selectMessagesToSync,
|
|
181
|
+
sql: Sql.selectMessagesToSync,
|
|
174
182
|
parameters: [timestampToString(makeSyncTimestamp(diff.value))],
|
|
175
183
|
}), Effect.map(({ rows }) => rows));
|
|
176
184
|
syncWorkerPostMessage({
|
package/dist/src/Evolu.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface Evolu<S extends Schema> {
|
|
|
27
27
|
readonly ownerActions: OwnerActions;
|
|
28
28
|
readonly ensureSchema: (tables: Tables) => void;
|
|
29
29
|
}
|
|
30
|
-
export declare const Evolu: <
|
|
30
|
+
export declare const Evolu: <S extends Schema>() => Context.Tag<Evolu<S>, Evolu<S>>;
|
|
31
31
|
type ErrorStore = Store<EvoluError | null>;
|
|
32
32
|
type OwnerStore = Store<Owner | null>;
|
|
33
33
|
interface QueryStore {
|
|
@@ -65,7 +65,7 @@ interface RestoreOwnerError {
|
|
|
65
65
|
readonly _tag: "RestoreOwnerError";
|
|
66
66
|
}
|
|
67
67
|
export declare const loadingPromisesPromiseProp = "rows";
|
|
68
|
-
export declare const EvoluLive: <
|
|
69
|
-
export declare const makeEvoluForPlatform: <
|
|
68
|
+
export declare const EvoluLive: <S extends Schema>(tables: Tables) => Layer.Layer<Config | Bip39 | NanoId | Time | FlushSync | AppState | DbWorker, never, Evolu<S>>;
|
|
69
|
+
export declare const makeEvoluForPlatform: <S extends Schema>(PlatformLayer: Layer.Layer<never, never, DbWorker | Bip39 | NanoId | FlushSync | AppState>, tables: Tables, config?: Partial<Config>) => Evolu<S>;
|
|
70
70
|
export {};
|
|
71
71
|
//# sourceMappingURL=Evolu.d.ts.map
|
package/dist/src/Model.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as Schema from "@effect/schema/Schema";
|
|
2
2
|
import { Brand } from "effect";
|
|
3
|
-
import { Row } from "./Sqlite.js";
|
|
4
3
|
/**
|
|
5
4
|
* Branded Id Schema for any table Id.
|
|
6
5
|
* To create Id Schema for a specific table, use {@link id}.
|
|
@@ -116,28 +115,4 @@ export type NonEmptyString1000 = Schema.Schema.To<typeof NonEmptyString1000>;
|
|
|
116
115
|
*/
|
|
117
116
|
export declare const PositiveInt: Schema.BrandSchema<number, number & Brand.Brand<"PositiveInt">>;
|
|
118
117
|
export type PositiveInt = Schema.Schema.To<typeof PositiveInt>;
|
|
119
|
-
/**
|
|
120
|
-
* Filter and map array items in one step with the correct return type and
|
|
121
|
-
* without unreliable TypeScript type guards.
|
|
122
|
-
*
|
|
123
|
-
* ### Examples
|
|
124
|
-
*
|
|
125
|
-
* ```
|
|
126
|
-
* useQuery(
|
|
127
|
-
* (db) => db.selectFrom("todo").selectAll(),
|
|
128
|
-
* // Filter and map nothing.
|
|
129
|
-
* (row) => row,
|
|
130
|
-
* );
|
|
131
|
-
*
|
|
132
|
-
* useQuery(
|
|
133
|
-
* (db) => db.selectFrom("todo").selectAll(),
|
|
134
|
-
* // Filter items with title != null.
|
|
135
|
-
* // Note the title type isn't nullable anymore in rows.
|
|
136
|
-
* ({ title, ...rest }) => title != null && { title, ...rest },
|
|
137
|
-
* );
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export type FilterMap<QueryRow extends Row, FilterMapRow extends Row> = (row: QueryRow) => OrNullOrFalse<FilterMapRow>;
|
|
141
|
-
export type OrNullOrFalse<T> = T | null | false;
|
|
142
|
-
export type ExcludeNullAndFalse<T> = Exclude<T, null | false>;
|
|
143
118
|
//# sourceMappingURL=Model.d.ts.map
|
package/dist/src/Model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Model.d.ts","sourceRoot":"","sources":["../../src/Model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAG/B;;;GAGG;AACH,eAAO,MAAM,EAAE,wDAGd,CAAC;AACF,MAAM,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,EAAE,yGAGe,CAAC;AAE/B;;;;GAIG;AACH,eAAO,MAAM,UAAU,gEAGtB,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC;AAE7D;;;;GAIG;AACH,eAAO,MAAM,aAAa,mEAIzB,CAAC;AACF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,aAAa,CAAC,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,GAC/C,OAAO,GAAG,aAAa,GACvB,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,aAAa,GACjC,IAAI,GAAG,OAAO,GAAG,aAAa,GAC9B,CAAC,CAAC,CAAC,CAAC,SAAS,UAAU,GACvB,IAAI,GAAG,UAAU,GACjB,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,UAAU,GAC9B,IAAI,GAAG,IAAI,GAAG,UAAU,GACxB,CAAC,CAAC,CAAC,CAAC;CACT,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAAC;AACpD,wBAAgB,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;AACpD,wBAAgB,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,UAAU,CAAC;AAC9C,wBAAgB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;AAW9C;;;;;GAKG;AACH,eAAO,MAAM,MAAM,4DAclB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,wFAGtB,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC;AAE7D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,gGAI9B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE7E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,iEAIvB,CAAC;AACF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC"}
|
package/dist/src/Sql.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare const createOwnerTable = "\nCREATE TABLE\n \"evolu_owner\" (\n
|
|
|
5
5
|
export declare const insertOwner = "\nINSERT INTO\n \"evolu_owner\" (\n \"id\",\n \"mnemonic\",\n \"encryptionKey\",\n \"timestamp\",\n \"merkleTree\"\n )\nVALUES\n (?, ?, ?, ?, ?);\n";
|
|
6
6
|
export declare const selectOwnerTimestampAndMerkleTree = "\nSELECT\n \"timestamp\",\n \"merkleTree\"\nFROM\n \"evolu_owner\"\n";
|
|
7
7
|
export declare const selectLastTimestampForTableRowColumn = "\nSELECT\n \"timestamp\"\nFROM\n \"evolu_message\"\nWHERE\n \"table\" = ?\n AND \"row\" = ?\n AND \"column\" = ?\nORDER BY\n \"timestamp\" DESC\nLIMIT\n 1\n";
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const upsertValueIntoTableRowColumn: (table: string, column: string) => string;
|
|
9
|
+
export declare const deleteTableRow: (table: string) => string;
|
|
9
10
|
export declare const insertIntoMessagesIfNew = "\nINSERT INTO\n \"evolu_message\" (\"timestamp\", \"table\", \"row\", \"column\", \"value\")\nVALUES\n (?, ?, ?, ?, ?)\nON CONFLICT DO NOTHING\n";
|
|
10
11
|
export declare const updateOwnerTimestampAndMerkleTree = "\nUPDATE \"evolu_owner\"\nSET\n \"timestamp\" = ?,\n \"merkleTree\" = ?\n";
|
|
11
12
|
export declare const selectMessagesToSync = "\nSELECT\n *\nFROM\n \"evolu_message\"\nWHERE\n \"timestamp\" >= ?\nORDER BY\n \"timestamp\"\n";
|
package/dist/src/Sql.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sql.d.ts","sourceRoot":"","sources":["../../src/Sql.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,yFAOvB,CAAC;AAEF,eAAO,MAAM,kBAAkB,yKAS9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,yHAInC,CAAC;AAEF,eAAO,MAAM,gBAAgB,yKAS5B,CAAC;AAEF,eAAO,MAAM,WAAW,0KAWvB,CAAC;AAEF,eAAO,MAAM,iCAAiC,4EAM7C,CAAC;AAEF,eAAO,MAAM,oCAAoC,wKAahD,CAAC;AAEF,eAAO,MAAM,6BAA6B,UACjC,MAAM,UACL,MAAM,KACb,MAOF,CAAC;AAEF,eAAO,MAAM,uBAAuB,uJAMnC,CAAC;AAEF,eAAO,MAAM,iCAAiC,gFAK7C,CAAC;AAEF,eAAO,MAAM,oBAAoB,uGAShC,CAAC"}
|
|
1
|
+
{"version":3,"file":"Sql.d.ts","sourceRoot":"","sources":["../../src/Sql.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,yFAOvB,CAAC;AAEF,eAAO,MAAM,kBAAkB,yKAS9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,yHAInC,CAAC;AAEF,eAAO,MAAM,gBAAgB,yKAS5B,CAAC;AAEF,eAAO,MAAM,WAAW,0KAWvB,CAAC;AAEF,eAAO,MAAM,iCAAiC,4EAM7C,CAAC;AAEF,eAAO,MAAM,oCAAoC,wKAahD,CAAC;AAEF,eAAO,MAAM,6BAA6B,UACjC,MAAM,UACL,MAAM,KACb,MAOF,CAAC;AAEF,eAAO,MAAM,cAAc,UAAW,MAAM,KAAG,MAI9C,CAAC;AAEF,eAAO,MAAM,uBAAuB,uJAMnC,CAAC;AAEF,eAAO,MAAM,iCAAiC,gFAK7C,CAAC;AAEF,eAAO,MAAM,oBAAoB,uGAShC,CAAC"}
|
package/dist/src/Sql.js
CHANGED
|
@@ -67,7 +67,7 @@ ORDER BY
|
|
|
67
67
|
LIMIT
|
|
68
68
|
1
|
|
69
69
|
`;
|
|
70
|
-
export const
|
|
70
|
+
export const upsertValueIntoTableRowColumn = (table, column) => `
|
|
71
71
|
INSERT INTO
|
|
72
72
|
"${table}" ("id", "${column}")
|
|
73
73
|
VALUES
|
|
@@ -75,6 +75,11 @@ VALUES
|
|
|
75
75
|
ON CONFLICT DO UPDATE SET
|
|
76
76
|
"${column}" = ?
|
|
77
77
|
`;
|
|
78
|
+
export const deleteTableRow = (table) => `
|
|
79
|
+
DELETE FROM "${table}"
|
|
80
|
+
WHERE
|
|
81
|
+
"id" = ?;
|
|
82
|
+
`;
|
|
78
83
|
export const insertIntoMessagesIfNew = `
|
|
79
84
|
INSERT INTO
|
|
80
85
|
"evolu_message" ("timestamp", "table", "row", "column", "value")
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,14 +4,11 @@ export * from "./Crdt.js";
|
|
|
4
4
|
export * from "./Crypto.js";
|
|
5
5
|
export * from "./Db.js";
|
|
6
6
|
export * from "./DbWorker.js";
|
|
7
|
-
export * from "./Diff.js";
|
|
8
7
|
export * from "./Errors.js";
|
|
9
8
|
export * from "./Evolu.js";
|
|
10
9
|
export * from "./Model.js";
|
|
11
|
-
export * from "./Murmurhash.js";
|
|
12
10
|
export * from "./Platform.js";
|
|
13
11
|
export * from "./Protobuf.js";
|
|
14
|
-
export * from "./Sql.js";
|
|
15
12
|
export * from "./Sqlite.js";
|
|
16
13
|
export * from "./Store.js";
|
|
17
14
|
export * from "./SyncWorker.js";
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtE,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtE,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -4,14 +4,11 @@ export * from "./Crdt.js";
|
|
|
4
4
|
export * from "./Crypto.js";
|
|
5
5
|
export * from "./Db.js";
|
|
6
6
|
export * from "./DbWorker.js";
|
|
7
|
-
export * from "./Diff.js";
|
|
8
7
|
export * from "./Errors.js";
|
|
9
8
|
export * from "./Evolu.js";
|
|
10
9
|
export * from "./Model.js";
|
|
11
|
-
export * from "./Murmurhash.js";
|
|
12
10
|
export * from "./Platform.js";
|
|
13
11
|
export * from "./Protobuf.js";
|
|
14
|
-
export * from "./Sql.js";
|
|
15
12
|
export * from "./Sqlite.js";
|
|
16
13
|
export * from "./Store.js";
|
|
17
14
|
export * from "./SyncWorker.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/common",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "Local-first platform designed for privacy, ease of use, and no vendor lock-in to sync and backup people's lifetime data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -42,11 +42,12 @@
|
|
|
42
42
|
"nanoid": "^5.0.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@effect/schema": "0.
|
|
45
|
+
"@effect/schema": "0.46.1",
|
|
46
46
|
"@protobuf-ts/plugin": "^2.9.1",
|
|
47
47
|
"@protobuf-ts/protoc": "^2.9.1",
|
|
48
48
|
"array-shuffle": "^3.0.0",
|
|
49
|
-
"
|
|
49
|
+
"better-sqlite3": "^9.0.0",
|
|
50
|
+
"effect": "2.0.0-next.52",
|
|
50
51
|
"eslint": "^8.52.0",
|
|
51
52
|
"typescript": "^5.2.2",
|
|
52
53
|
"vitest": "^0.34.6",
|
|
@@ -54,8 +55,8 @@
|
|
|
54
55
|
"eslint-config-evolu": "0.0.2"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
|
-
"@effect/schema": "0.
|
|
58
|
-
"effect": "2.0.0-next.
|
|
58
|
+
"@effect/schema": "0.46.1",
|
|
59
|
+
"effect": "2.0.0-next.52"
|
|
59
60
|
},
|
|
60
61
|
"publishConfig": {
|
|
61
62
|
"access": "public"
|
package/src/Db.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
pipe,
|
|
16
16
|
} from "effect";
|
|
17
17
|
import * as Kysely from "kysely";
|
|
18
|
+
import { Simplify } from "kysely";
|
|
18
19
|
import { urlAlphabet } from "nanoid";
|
|
19
20
|
import {
|
|
20
21
|
initialMerkleTree,
|
|
@@ -50,13 +51,13 @@ export type CreateQuery<S extends Schema> = (
|
|
|
50
51
|
) => Query;
|
|
51
52
|
|
|
52
53
|
export type QueryCallback<S extends Schema, QueryRow> = (
|
|
53
|
-
db: KyselyWithoutMutation<
|
|
54
|
+
db: KyselyWithoutMutation<QuerySchema<S>>,
|
|
54
55
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
56
|
) => Kysely.SelectQueryBuilder<any, any, QueryRow>;
|
|
56
57
|
|
|
57
58
|
type KyselyWithoutMutation<DB> = Pick<Kysely.Kysely<DB>, "selectFrom" | "fn">;
|
|
58
59
|
|
|
59
|
-
type
|
|
60
|
+
type QuerySchema<S extends Schema> = {
|
|
60
61
|
readonly [Table in keyof S]: NullableExceptOfId<
|
|
61
62
|
{
|
|
62
63
|
readonly [Column in keyof S[Table]]: S[Table][Column];
|
|
@@ -74,6 +75,48 @@ export interface CommonColumns {
|
|
|
74
75
|
readonly isDeleted: SqliteBoolean;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Filter and map array items in one step with the correct return type and
|
|
80
|
+
* without unreliable TypeScript type guards.
|
|
81
|
+
*
|
|
82
|
+
* ### Examples
|
|
83
|
+
*
|
|
84
|
+
* ```
|
|
85
|
+
* useQuery(
|
|
86
|
+
* (db) => db.selectFrom("todo").selectAll(),
|
|
87
|
+
* // Filter and map nothing.
|
|
88
|
+
* (row) => row,
|
|
89
|
+
* );
|
|
90
|
+
*
|
|
91
|
+
* useQuery(
|
|
92
|
+
* (db) => db.selectFrom("todo").selectAll(),
|
|
93
|
+
* // Filter items with title != null.
|
|
94
|
+
* // Note the title type isn't nullable anymore in rows.
|
|
95
|
+
* ({ title, ...rest }) => title != null && { title, ...rest },
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export type FilterMap<QueryRow extends Row, FilterMapRow extends Row> = (
|
|
100
|
+
row: QueryRow,
|
|
101
|
+
) => FilterMapRow | null | false;
|
|
102
|
+
|
|
103
|
+
export interface QueryResult<FilterMapRow extends Row> {
|
|
104
|
+
/**
|
|
105
|
+
* Rows from the database. They can be filtered and mapped by `filterMap`.
|
|
106
|
+
*/
|
|
107
|
+
readonly rows: ReadonlyArray<
|
|
108
|
+
Readonly<Simplify<ExcludeNullAndFalse<FilterMapRow>>>
|
|
109
|
+
>;
|
|
110
|
+
/**
|
|
111
|
+
* The first row from `rows`. For empty rows, it's null.
|
|
112
|
+
*/
|
|
113
|
+
readonly firstRow: Readonly<
|
|
114
|
+
Simplify<ExcludeNullAndFalse<FilterMapRow>>
|
|
115
|
+
> | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type ExcludeNullAndFalse<T> = Exclude<T, null | false>;
|
|
119
|
+
|
|
77
120
|
export interface Table {
|
|
78
121
|
readonly name: string;
|
|
79
122
|
readonly columns: ReadonlyArray<string>;
|
|
@@ -83,7 +126,7 @@ export type Tables = ReadonlyArray<Table>;
|
|
|
83
126
|
|
|
84
127
|
const commonColumns = ["createdAt", "updatedAt", "isDeleted"];
|
|
85
128
|
|
|
86
|
-
const kysely: Kysely.Kysely<
|
|
129
|
+
const kysely: Kysely.Kysely<QuerySchema<Schema>> = new Kysely.Kysely({
|
|
87
130
|
dialect: {
|
|
88
131
|
createAdapter: () => new Kysely.SqliteAdapter(),
|
|
89
132
|
createDriver: () => new Kysely.DummyDriver(),
|
|
@@ -95,7 +138,7 @@ const kysely: Kysely.Kysely<SchemaForQuery<Schema>> = new Kysely.Kysely({
|
|
|
95
138
|
});
|
|
96
139
|
|
|
97
140
|
export const makeCreateQuery =
|
|
98
|
-
<
|
|
141
|
+
<S extends Schema>(): CreateQuery<S> =>
|
|
99
142
|
(queryCallback) =>
|
|
100
143
|
queryObjectToQuery(queryCallback(kysely as never).compile() as QueryObject);
|
|
101
144
|
|
|
@@ -169,26 +212,26 @@ export interface NoSuchTableOrColumnError {
|
|
|
169
212
|
readonly _tag: "NoSuchTableOrColumnError";
|
|
170
213
|
}
|
|
171
214
|
|
|
172
|
-
export const
|
|
173
|
-
(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
error.message.includes("has no column"))
|
|
183
|
-
)
|
|
184
|
-
return Option.some(
|
|
185
|
-
Effect.fail<NoSuchTableOrColumnError>({
|
|
186
|
-
_tag: "NoSuchTableOrColumnError",
|
|
187
|
-
}),
|
|
188
|
-
);
|
|
215
|
+
export const SqliteNoSuchTableOrColumnError = S.struct({
|
|
216
|
+
message: S.union(
|
|
217
|
+
S.string.pipe(S.includes("no such table")),
|
|
218
|
+
S.string.pipe(S.includes("no such column")),
|
|
219
|
+
S.string.pipe(S.includes("has no column")),
|
|
220
|
+
),
|
|
221
|
+
});
|
|
222
|
+
export type SqliteNoSuchTableOrColumnError = S.Schema.To<
|
|
223
|
+
typeof SqliteNoSuchTableOrColumnError
|
|
224
|
+
>;
|
|
189
225
|
|
|
190
|
-
|
|
191
|
-
|
|
226
|
+
export const someDefectToNoSuchTableOrColumnError = Effect.catchSomeDefect(
|
|
227
|
+
(error) =>
|
|
228
|
+
S.is(SqliteNoSuchTableOrColumnError)(error)
|
|
229
|
+
? Option.some(
|
|
230
|
+
Effect.fail<NoSuchTableOrColumnError>({
|
|
231
|
+
_tag: "NoSuchTableOrColumnError",
|
|
232
|
+
}),
|
|
233
|
+
)
|
|
234
|
+
: Option.none(),
|
|
192
235
|
);
|
|
193
236
|
|
|
194
237
|
export const makeOwner = (
|
|
@@ -325,3 +368,24 @@ export const ensureSchema = (
|
|
|
325
368
|
{ discard: true },
|
|
326
369
|
),
|
|
327
370
|
);
|
|
371
|
+
|
|
372
|
+
export const makeCacheFilterMap = (): (<
|
|
373
|
+
QueryRow extends Row,
|
|
374
|
+
FilterMapRow extends Row,
|
|
375
|
+
>(
|
|
376
|
+
filterMap: FilterMap<QueryRow, FilterMapRow>,
|
|
377
|
+
) => FilterMap<QueryRow, FilterMapRow>) => {
|
|
378
|
+
const cache = new WeakMap<Row, Row | null | false>();
|
|
379
|
+
|
|
380
|
+
return <QueryRow extends Row, FilterMapRow extends Row>(
|
|
381
|
+
filterMap: FilterMap<QueryRow, FilterMapRow>,
|
|
382
|
+
): FilterMap<QueryRow, FilterMapRow> =>
|
|
383
|
+
(row: QueryRow) => {
|
|
384
|
+
let cachedRow = cache.get(row);
|
|
385
|
+
if (cachedRow === undefined) {
|
|
386
|
+
cachedRow = filterMap(row);
|
|
387
|
+
cache.set(row, cachedRow);
|
|
388
|
+
}
|
|
389
|
+
return cachedRow as FilterMapRow | null | false;
|
|
390
|
+
};
|
|
391
|
+
};
|
package/src/DbWorker.ts
CHANGED
|
@@ -45,15 +45,7 @@ import {
|
|
|
45
45
|
import { QueryPatches, makePatches } from "./Diff.js";
|
|
46
46
|
import { EvoluError, UnexpectedError, makeUnexpectedError } from "./Errors.js";
|
|
47
47
|
import { Id, SqliteDate, cast } from "./Model.js";
|
|
48
|
-
import
|
|
49
|
-
insertIntoMessagesIfNew,
|
|
50
|
-
insertValueIntoTableRowColumn,
|
|
51
|
-
selectLastTimestampForTableRowColumn,
|
|
52
|
-
selectMessagesToSync,
|
|
53
|
-
selectOwner,
|
|
54
|
-
selectOwnerTimestampAndMerkleTree,
|
|
55
|
-
updateOwnerTimestampAndMerkleTree,
|
|
56
|
-
} from "./Sql.js";
|
|
48
|
+
import * as Sql from "./Sql.js";
|
|
57
49
|
import { Query, Row, Sqlite, Value, queryObjectFromQuery } from "./Sqlite.js";
|
|
58
50
|
import {
|
|
59
51
|
Message,
|
|
@@ -179,7 +171,7 @@ const init = (
|
|
|
179
171
|
const sqlite = yield* _(Sqlite);
|
|
180
172
|
|
|
181
173
|
return yield* _(
|
|
182
|
-
sqlite.exec(selectOwner),
|
|
174
|
+
sqlite.exec(Sql.selectOwner),
|
|
183
175
|
Effect.map(
|
|
184
176
|
({ rows: [row] }): Owner => ({
|
|
185
177
|
id: row.id as OwnerId,
|
|
@@ -237,7 +229,9 @@ interface TimestampAndMerkleTree {
|
|
|
237
229
|
}
|
|
238
230
|
|
|
239
231
|
const readTimestampAndMerkleTree = Sqlite.pipe(
|
|
240
|
-
Effect.flatMap((sqlite) =>
|
|
232
|
+
Effect.flatMap((sqlite) =>
|
|
233
|
+
sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree),
|
|
234
|
+
),
|
|
241
235
|
Effect.map((result) => result.rows),
|
|
242
236
|
Effect.map(
|
|
243
237
|
([{ timestamp, merkleTree }]): TimestampAndMerkleTree => ({
|
|
@@ -248,11 +242,11 @@ const readTimestampAndMerkleTree = Sqlite.pipe(
|
|
|
248
242
|
);
|
|
249
243
|
|
|
250
244
|
export const mutateItemsToNewMessages = (
|
|
251
|
-
items: ReadonlyArray
|
|
252
|
-
): ReadonlyArray
|
|
245
|
+
items: ReadonlyArray<MutateItem>,
|
|
246
|
+
): ReadonlyArray<NewMessage> =>
|
|
253
247
|
pipe(
|
|
254
248
|
items,
|
|
255
|
-
ReadonlyArray.
|
|
249
|
+
ReadonlyArray.map(({ id, isInsert, now, table, values }) =>
|
|
256
250
|
pipe(
|
|
257
251
|
Object.entries(values),
|
|
258
252
|
ReadonlyArray.filterMap(([key, value]) =>
|
|
@@ -274,7 +268,7 @@ export const mutateItemsToNewMessages = (
|
|
|
274
268
|
] as const,
|
|
275
269
|
),
|
|
276
270
|
ReadonlyArray.append([isInsert ? "createdAt" : "updatedAt", now]),
|
|
277
|
-
ReadonlyArray.
|
|
271
|
+
ReadonlyArray.map(
|
|
278
272
|
([key, value]): NewMessage => ({
|
|
279
273
|
table,
|
|
280
274
|
row: id,
|
|
@@ -284,12 +278,12 @@ export const mutateItemsToNewMessages = (
|
|
|
284
278
|
),
|
|
285
279
|
),
|
|
286
280
|
),
|
|
287
|
-
ReadonlyArray.
|
|
288
|
-
ReadonlyArray.
|
|
281
|
+
ReadonlyArray.flatten,
|
|
282
|
+
ReadonlyArray.dedupeWith(NewMessageEquivalence),
|
|
289
283
|
);
|
|
290
284
|
|
|
291
|
-
const
|
|
292
|
-
messages: ReadonlyArray
|
|
285
|
+
const ensureSchemaByNewMessages = (
|
|
286
|
+
messages: ReadonlyArray<NewMessage>,
|
|
293
287
|
): Effect.Effect<Sqlite, never, void> =>
|
|
294
288
|
Effect.gen(function* (_) {
|
|
295
289
|
const tablesMap = new Map<string, Table>();
|
|
@@ -311,12 +305,34 @@ const ensureSchemaByMessages = (
|
|
|
311
305
|
yield* _(ensureSchema(Array.from(tablesMap.values())));
|
|
312
306
|
});
|
|
313
307
|
|
|
308
|
+
export const upsertValueIntoTableRowColumn = (
|
|
309
|
+
message: NewMessage,
|
|
310
|
+
messages: ReadonlyArray<NewMessage>,
|
|
311
|
+
): Effect.Effect<Sqlite, never, void> =>
|
|
312
|
+
Effect.gen(function* (_) {
|
|
313
|
+
const sqlite = yield* _(Sqlite);
|
|
314
|
+
|
|
315
|
+
const insert = sqlite.exec({
|
|
316
|
+
sql: Sql.upsertValueIntoTableRowColumn(message.table, message.column),
|
|
317
|
+
parameters: [message.row, message.value, message.value],
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
yield* _(
|
|
321
|
+
insert,
|
|
322
|
+
someDefectToNoSuchTableOrColumnError,
|
|
323
|
+
Effect.catchTag("NoSuchTableOrColumnError", () =>
|
|
324
|
+
// If one message fails, we ensure schema for all messages.
|
|
325
|
+
ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)),
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
314
330
|
const applyMessages = ({
|
|
315
331
|
merkleTree,
|
|
316
332
|
messages,
|
|
317
333
|
}: {
|
|
318
334
|
merkleTree: MerkleTree;
|
|
319
|
-
messages: ReadonlyArray
|
|
335
|
+
messages: ReadonlyArray<Message>;
|
|
320
336
|
}): Effect.Effect<Sqlite, never, MerkleTree> =>
|
|
321
337
|
Effect.gen(function* (_) {
|
|
322
338
|
const sqlite = yield* _(Sqlite);
|
|
@@ -324,7 +340,7 @@ const applyMessages = ({
|
|
|
324
340
|
for (const message of messages) {
|
|
325
341
|
const timestamp: TimestampString | null = yield* _(
|
|
326
342
|
sqlite.exec({
|
|
327
|
-
sql: selectLastTimestampForTableRowColumn,
|
|
343
|
+
sql: Sql.selectLastTimestampForTableRowColumn,
|
|
328
344
|
parameters: [message.table, message.row, message.column],
|
|
329
345
|
}),
|
|
330
346
|
Effect.map((result) => result.rows),
|
|
@@ -334,23 +350,13 @@ const applyMessages = ({
|
|
|
334
350
|
);
|
|
335
351
|
|
|
336
352
|
if (timestamp == null || timestamp < message.timestamp) {
|
|
337
|
-
|
|
338
|
-
sql: insertValueIntoTableRowColumn(message.table, message.column),
|
|
339
|
-
parameters: [message.row, message.value, message.value],
|
|
340
|
-
});
|
|
341
|
-
yield* _(
|
|
342
|
-
insert,
|
|
343
|
-
someDefectToNoSuchTableOrColumnError,
|
|
344
|
-
Effect.catchTag("NoSuchTableOrColumnError", () =>
|
|
345
|
-
ensureSchemaByMessages(messages).pipe(Effect.flatMap(() => insert)),
|
|
346
|
-
),
|
|
347
|
-
);
|
|
353
|
+
yield* _(upsertValueIntoTableRowColumn(message, messages));
|
|
348
354
|
}
|
|
349
355
|
|
|
350
356
|
if (timestamp == null || timestamp !== message.timestamp) {
|
|
351
357
|
const { changes } = yield* _(
|
|
352
358
|
sqlite.exec({
|
|
353
|
-
sql: insertIntoMessagesIfNew,
|
|
359
|
+
sql: Sql.insertIntoMessagesIfNew,
|
|
354
360
|
parameters: [
|
|
355
361
|
message.timestamp,
|
|
356
362
|
message.table,
|
|
@@ -360,11 +366,10 @@ const applyMessages = ({
|
|
|
360
366
|
],
|
|
361
367
|
}),
|
|
362
368
|
);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
merkleTree = insertIntoMerkleTree(
|
|
366
|
-
|
|
367
|
-
)(merkleTree);
|
|
369
|
+
if (changes > 0) {
|
|
370
|
+
const timestamp = unsafeTimestampFromString(message.timestamp);
|
|
371
|
+
merkleTree = insertIntoMerkleTree(timestamp)(merkleTree);
|
|
372
|
+
}
|
|
368
373
|
}
|
|
369
374
|
}
|
|
370
375
|
|
|
@@ -377,7 +382,7 @@ const writeTimestampAndMerkleTree = ({
|
|
|
377
382
|
}: TimestampAndMerkleTree): Effect.Effect<Sqlite, never, void> =>
|
|
378
383
|
Effect.flatMap(Sqlite, (sqlite) =>
|
|
379
384
|
sqlite.exec({
|
|
380
|
-
sql: updateOwnerTimestampAndMerkleTree,
|
|
385
|
+
sql: Sql.updateOwnerTimestampAndMerkleTree,
|
|
381
386
|
parameters: [
|
|
382
387
|
timestampToString(timestamp),
|
|
383
388
|
merkleTreeToString(merkleTree),
|
|
@@ -402,47 +407,63 @@ const mutate = ({
|
|
|
402
407
|
void
|
|
403
408
|
> =>
|
|
404
409
|
Effect.gen(function* (_) {
|
|
405
|
-
const
|
|
406
|
-
|
|
410
|
+
const [toSync, localOnlyItems] = ReadonlyArray.partition(items, (item) =>
|
|
411
|
+
item.table.startsWith("_"),
|
|
412
|
+
);
|
|
413
|
+
const [toUpsert, toDelete] = ReadonlyArray.partition(
|
|
414
|
+
localOnlyItems,
|
|
415
|
+
(item) =>
|
|
416
|
+
mutateItemsToNewMessages([item]).some(
|
|
417
|
+
(message) => message.column === "isDeleted" && message.value === 1,
|
|
418
|
+
),
|
|
419
|
+
).map(mutateItemsToNewMessages);
|
|
407
420
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
Effect.map(sendTimestamp(timestamp), (nextTimestamp): Message => {
|
|
412
|
-
timestamp = nextTimestamp;
|
|
413
|
-
return {
|
|
414
|
-
...newMessage,
|
|
415
|
-
timestamp: timestampToString(timestamp),
|
|
416
|
-
};
|
|
417
|
-
}),
|
|
421
|
+
yield* _(
|
|
422
|
+
Effect.forEach(toUpsert, (message) =>
|
|
423
|
+
upsertValueIntoTableRowColumn(message, toUpsert),
|
|
418
424
|
),
|
|
419
425
|
);
|
|
420
426
|
|
|
421
|
-
|
|
427
|
+
const { exec } = yield* _(Sqlite);
|
|
428
|
+
yield* _(
|
|
429
|
+
Effect.forEach(toDelete, ({ table, row }) =>
|
|
430
|
+
exec({ sql: Sql.deleteTableRow(table), parameters: [row] }),
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
if (toSync.length > 0) {
|
|
435
|
+
let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
|
|
436
|
+
|
|
437
|
+
const messages = yield* _(
|
|
438
|
+
mutateItemsToNewMessages(toSync),
|
|
439
|
+
Effect.forEach((message) =>
|
|
440
|
+
Effect.map(sendTimestamp(timestamp), (nextTimestamp): Message => {
|
|
441
|
+
timestamp = nextTimestamp;
|
|
442
|
+
return { ...message, timestamp: timestampToString(timestamp) };
|
|
443
|
+
}),
|
|
444
|
+
),
|
|
445
|
+
);
|
|
446
|
+
|
|
422
447
|
merkleTree = yield* _(applyMessages({ merkleTree, messages }));
|
|
448
|
+
|
|
423
449
|
yield* _(writeTimestampAndMerkleTree({ timestamp, merkleTree }));
|
|
450
|
+
|
|
451
|
+
(yield* _(SyncWorkerPostMessage))({
|
|
452
|
+
_tag: "sync",
|
|
453
|
+
syncUrl: (yield* _(Config)).syncUrl,
|
|
454
|
+
messages,
|
|
455
|
+
timestamp,
|
|
456
|
+
merkleTree,
|
|
457
|
+
owner: yield* _(Owner),
|
|
458
|
+
syncLoopCount: 0,
|
|
459
|
+
});
|
|
424
460
|
}
|
|
425
461
|
|
|
426
462
|
const onCompleteIds = ReadonlyArray.filterMap(items, (item) =>
|
|
427
463
|
Option.fromNullable(item.onCompleteId),
|
|
428
464
|
);
|
|
429
|
-
|
|
430
465
|
if (queries.length > 0 || onCompleteIds.length > 0)
|
|
431
466
|
yield* _(query({ queries, onCompleteIds }));
|
|
432
|
-
|
|
433
|
-
const [config, syncWorkerPostMessage] = yield* _(
|
|
434
|
-
Effect.all([Config, SyncWorkerPostMessage]),
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
syncWorkerPostMessage({
|
|
438
|
-
_tag: "sync",
|
|
439
|
-
syncUrl: config.syncUrl,
|
|
440
|
-
messages,
|
|
441
|
-
timestamp,
|
|
442
|
-
merkleTree,
|
|
443
|
-
owner,
|
|
444
|
-
syncLoopCount: 0,
|
|
445
|
-
});
|
|
446
467
|
});
|
|
447
468
|
|
|
448
469
|
const handleSyncResponse = ({
|
|
@@ -456,7 +477,7 @@ const handleSyncResponse = ({
|
|
|
456
477
|
Effect.gen(function* (_) {
|
|
457
478
|
let { timestamp, merkleTree } = yield* _(readTimestampAndMerkleTree);
|
|
458
479
|
|
|
459
|
-
if (
|
|
480
|
+
if (messages.length > 0) {
|
|
460
481
|
for (const message of messages)
|
|
461
482
|
timestamp = yield* _(
|
|
462
483
|
unsafeTimestampFromString(message.timestamp),
|
|
@@ -490,7 +511,7 @@ const handleSyncResponse = ({
|
|
|
490
511
|
);
|
|
491
512
|
const messagesToSync = yield* _(
|
|
492
513
|
sqlite.exec({
|
|
493
|
-
sql: selectMessagesToSync,
|
|
514
|
+
sql: Sql.selectMessagesToSync,
|
|
494
515
|
parameters: [timestampToString(makeSyncTimestamp(diff.value))],
|
|
495
516
|
}),
|
|
496
517
|
Effect.map(({ rows }) => rows as unknown as ReadonlyArray<Message>),
|
package/src/Evolu.ts
CHANGED
|
@@ -60,8 +60,8 @@ export interface Evolu<S extends Schema> {
|
|
|
60
60
|
readonly ensureSchema: (tables: Tables) => void;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export const Evolu = <
|
|
64
|
-
Context.Tag<Evolu<
|
|
63
|
+
export const Evolu = <S extends Schema>(): Context.Tag<Evolu<S>, Evolu<S>> =>
|
|
64
|
+
Context.Tag<Evolu<S>>("evolu/Evolu");
|
|
65
65
|
|
|
66
66
|
type ErrorStore = Store<EvoluError | null>;
|
|
67
67
|
|
|
@@ -118,8 +118,8 @@ type Mutate<S extends Schema> = <
|
|
|
118
118
|
readonly id: U[T]["id"];
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
const Mutate = <
|
|
122
|
-
Context.Tag<Mutate<
|
|
121
|
+
const Mutate = <S extends Schema>(): Context.Tag<Mutate<S>, Mutate<S>> =>
|
|
122
|
+
Context.Tag<Mutate<S>>("evolu/Mutate");
|
|
123
123
|
|
|
124
124
|
type SchemaForMutate<S extends Schema> = {
|
|
125
125
|
readonly [Table in keyof S]: NullableExceptOfId<
|
|
@@ -297,13 +297,13 @@ const QueryStoreLive = Layer.effect(
|
|
|
297
297
|
}),
|
|
298
298
|
);
|
|
299
299
|
|
|
300
|
-
const MutateLive = <
|
|
300
|
+
const MutateLive = <S extends Schema>(): Layer.Layer<
|
|
301
301
|
NanoId | OnCompletes | Time | SubscribedQueries | LoadingPromises | DbWorker,
|
|
302
302
|
never,
|
|
303
|
-
Mutate<
|
|
303
|
+
Mutate<S>
|
|
304
304
|
> =>
|
|
305
305
|
Layer.effect(
|
|
306
|
-
Mutate<
|
|
306
|
+
Mutate<S>(),
|
|
307
307
|
Effect.gen(function* (_) {
|
|
308
308
|
const nanoid = yield* _(NanoId);
|
|
309
309
|
const onCompletes = yield* _(OnCompletes);
|
|
@@ -314,7 +314,7 @@ const MutateLive = <T extends Schema>(): Layer.Layer<
|
|
|
314
314
|
|
|
315
315
|
const queue: Array<MutateItem> = [];
|
|
316
316
|
|
|
317
|
-
return Mutate<
|
|
317
|
+
return Mutate<S>().of((table, { id, ...values }, onComplete) => {
|
|
318
318
|
const isInsert = id == null;
|
|
319
319
|
if (isInsert) id = Effect.runSync(nanoid.nanoid) as never;
|
|
320
320
|
|
|
@@ -381,15 +381,15 @@ const OwnerActionsLive = Layer.effect(
|
|
|
381
381
|
}),
|
|
382
382
|
);
|
|
383
383
|
|
|
384
|
-
export const EvoluLive = <
|
|
384
|
+
export const EvoluLive = <S extends Schema>(
|
|
385
385
|
tables: Tables,
|
|
386
386
|
): Layer.Layer<
|
|
387
387
|
DbWorker | Bip39 | Config | FlushSync | NanoId | Time | AppState,
|
|
388
388
|
never,
|
|
389
|
-
Evolu<
|
|
389
|
+
Evolu<S>
|
|
390
390
|
> =>
|
|
391
391
|
Layer.effect(
|
|
392
|
-
Evolu<
|
|
392
|
+
Evolu<S>(),
|
|
393
393
|
Effect.gen(function* (_) {
|
|
394
394
|
const dbWorker = yield* _(DbWorker);
|
|
395
395
|
const appState = yield* _(AppState);
|
|
@@ -425,8 +425,8 @@ export const EvoluLive = <T extends Schema>(
|
|
|
425
425
|
).pipe(Effect.runSync);
|
|
426
426
|
|
|
427
427
|
const mutate = Effect.provide(
|
|
428
|
-
Mutate<
|
|
429
|
-
Layer.use(MutateLive<
|
|
428
|
+
Mutate<S>(),
|
|
429
|
+
Layer.use(MutateLive<S>(), Layers),
|
|
430
430
|
).pipe(Effect.runSync);
|
|
431
431
|
|
|
432
432
|
const ownerActions = Effect.provide(
|
|
@@ -434,7 +434,7 @@ export const EvoluLive = <T extends Schema>(
|
|
|
434
434
|
Layer.use(OwnerActionsLive, Layers),
|
|
435
435
|
).pipe(Effect.runSync);
|
|
436
436
|
|
|
437
|
-
const ensureSchema: Evolu<
|
|
437
|
+
const ensureSchema: Evolu<S>["ensureSchema"] = (tables) => {
|
|
438
438
|
dbWorker.postMessage({ _tag: "ensureSchema", tables });
|
|
439
439
|
};
|
|
440
440
|
|
|
@@ -486,14 +486,14 @@ export const EvoluLive = <T extends Schema>(
|
|
|
486
486
|
appState.onReconnect(sync);
|
|
487
487
|
sync();
|
|
488
488
|
|
|
489
|
-
return Evolu<
|
|
489
|
+
return Evolu<S>().of({
|
|
490
490
|
subscribeError: errorStore.subscribe,
|
|
491
491
|
getError: errorStore.getState,
|
|
492
492
|
|
|
493
493
|
subscribeOwner: ownerStore.subscribe,
|
|
494
494
|
getOwner: ownerStore.getState,
|
|
495
495
|
|
|
496
|
-
createQuery: makeCreateQuery<
|
|
496
|
+
createQuery: makeCreateQuery<S>(),
|
|
497
497
|
subscribeQuery: queryStore.subscribe,
|
|
498
498
|
getQuery: queryStore.getState,
|
|
499
499
|
loadQuery: queryStore.loadQuery,
|
|
@@ -501,15 +501,15 @@ export const EvoluLive = <T extends Schema>(
|
|
|
501
501
|
subscribeSyncState: syncStateStore.subscribe,
|
|
502
502
|
getSyncState: syncStateStore.getState,
|
|
503
503
|
|
|
504
|
-
create: mutate as Create<
|
|
505
|
-
update: mutate as Update<
|
|
504
|
+
create: mutate as Create<S>,
|
|
505
|
+
update: mutate as Update<S>,
|
|
506
506
|
ownerActions,
|
|
507
507
|
ensureSchema,
|
|
508
508
|
});
|
|
509
509
|
}),
|
|
510
510
|
);
|
|
511
511
|
|
|
512
|
-
export const makeEvoluForPlatform = <
|
|
512
|
+
export const makeEvoluForPlatform = <S extends Schema>(
|
|
513
513
|
PlatformLayer: Layer.Layer<
|
|
514
514
|
never,
|
|
515
515
|
never,
|
|
@@ -517,11 +517,11 @@ export const makeEvoluForPlatform = <T extends Schema>(
|
|
|
517
517
|
>,
|
|
518
518
|
tables: Tables,
|
|
519
519
|
config?: Partial<Config>,
|
|
520
|
-
): Evolu<
|
|
520
|
+
): Evolu<S> =>
|
|
521
521
|
Effect.provide(
|
|
522
|
-
Evolu<
|
|
522
|
+
Evolu<S>(),
|
|
523
523
|
Layer.use(
|
|
524
|
-
EvoluLive<
|
|
524
|
+
EvoluLive<S>(tables),
|
|
525
525
|
Layer.mergeAll(PlatformLayer, ConfigLive(config), TimeLive),
|
|
526
526
|
),
|
|
527
527
|
).pipe(Effect.runSync);
|
package/src/Model.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Schema from "@effect/schema/Schema";
|
|
2
2
|
import { Brand } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import { maybeJson } from "./Sqlite.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Branded Id Schema for any table Id.
|
|
@@ -181,32 +181,3 @@ export const PositiveInt = Schema.number.pipe(
|
|
|
181
181
|
Schema.brand("PositiveInt"),
|
|
182
182
|
);
|
|
183
183
|
export type PositiveInt = Schema.Schema.To<typeof PositiveInt>;
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Filter and map array items in one step with the correct return type and
|
|
187
|
-
* without unreliable TypeScript type guards.
|
|
188
|
-
*
|
|
189
|
-
* ### Examples
|
|
190
|
-
*
|
|
191
|
-
* ```
|
|
192
|
-
* useQuery(
|
|
193
|
-
* (db) => db.selectFrom("todo").selectAll(),
|
|
194
|
-
* // Filter and map nothing.
|
|
195
|
-
* (row) => row,
|
|
196
|
-
* );
|
|
197
|
-
*
|
|
198
|
-
* useQuery(
|
|
199
|
-
* (db) => db.selectFrom("todo").selectAll(),
|
|
200
|
-
* // Filter items with title != null.
|
|
201
|
-
* // Note the title type isn't nullable anymore in rows.
|
|
202
|
-
* ({ title, ...rest }) => title != null && { title, ...rest },
|
|
203
|
-
* );
|
|
204
|
-
* ```
|
|
205
|
-
*/
|
|
206
|
-
export type FilterMap<QueryRow extends Row, FilterMapRow extends Row> = (
|
|
207
|
-
row: QueryRow,
|
|
208
|
-
) => OrNullOrFalse<FilterMapRow>;
|
|
209
|
-
|
|
210
|
-
export type OrNullOrFalse<T> = T | null | false;
|
|
211
|
-
|
|
212
|
-
export type ExcludeNullAndFalse<T> = Exclude<T, null | false>;
|
package/src/Sql.ts
CHANGED
|
@@ -75,7 +75,7 @@ LIMIT
|
|
|
75
75
|
1
|
|
76
76
|
`;
|
|
77
77
|
|
|
78
|
-
export const
|
|
78
|
+
export const upsertValueIntoTableRowColumn = (
|
|
79
79
|
table: string,
|
|
80
80
|
column: string,
|
|
81
81
|
): string => `
|
|
@@ -87,6 +87,12 @@ ON CONFLICT DO UPDATE SET
|
|
|
87
87
|
"${column}" = ?
|
|
88
88
|
`;
|
|
89
89
|
|
|
90
|
+
export const deleteTableRow = (table: string): string => `
|
|
91
|
+
DELETE FROM "${table}"
|
|
92
|
+
WHERE
|
|
93
|
+
"id" = ?;
|
|
94
|
+
`;
|
|
95
|
+
|
|
90
96
|
export const insertIntoMessagesIfNew = `
|
|
91
97
|
INSERT INTO
|
|
92
98
|
"evolu_message" ("timestamp", "table", "row", "column", "value")
|
package/src/index.ts
CHANGED
|
@@ -4,14 +4,11 @@ export * from "./Crdt.js";
|
|
|
4
4
|
export * from "./Crypto.js";
|
|
5
5
|
export * from "./Db.js";
|
|
6
6
|
export * from "./DbWorker.js";
|
|
7
|
-
export * from "./Diff.js";
|
|
8
7
|
export * from "./Errors.js";
|
|
9
8
|
export * from "./Evolu.js";
|
|
10
9
|
export * from "./Model.js";
|
|
11
|
-
export * from "./Murmurhash.js";
|
|
12
10
|
export * from "./Platform.js";
|
|
13
11
|
export * from "./Protobuf.js";
|
|
14
|
-
export * from "./Sql.js";
|
|
15
12
|
export * from "./Sqlite.js";
|
|
16
13
|
export * from "./Store.js";
|
|
17
14
|
export * from "./SyncWorker.js";
|